From fc666e2e482190c13016c1a9735eb7a513c146a5 Mon Sep 17 00:00:00 2001 From: chrislu Date: Sun, 10 Aug 2025 12:37:18 -0700 Subject: [PATCH] collect ec volume deleted bytes --- .../create_vacuum_test_data.go | 26 +- docker/admin_integration/ec_test_files.json | 59 ++-- weed/admin/dash/ec_shard_management.go | 168 +++++++++- weed/admin/dash/types.go | 16 + weed/admin/view/app/ec_volume_details.templ | 95 +++++- .../admin/view/app/ec_volume_details_templ.go | 303 ++++++++++++------ 6 files changed, 533 insertions(+), 134 deletions(-) diff --git a/docker/admin_integration/create_vacuum_test_data.go b/docker/admin_integration/create_vacuum_test_data.go index 5f43a9744..0d7490394 100644 --- a/docker/admin_integration/create_vacuum_test_data.go +++ b/docker/admin_integration/create_vacuum_test_data.go @@ -472,9 +472,14 @@ type VolumeInfo struct { } type VolumeStatus struct { - IsLeader bool `json:"IsLeader"` - Leader string `json:"Leader"` - Volumes []VolumeInfo `json:"Volumes"` + Version string `json:"Version"` + Volumes VolumeLayout `json:"Volumes"` +} + +type VolumeLayout struct { + DataCenters map[string]map[string]map[string][]VolumeInfo `json:"DataCenters"` + Free int `json:"Free"` + Max int `json:"Max"` } func getVolumeStatusForDeletion() []VolumeInfo { @@ -492,7 +497,20 @@ func getVolumeStatusForDeletion() []VolumeInfo { return nil } - return volumeStatus.Volumes + // Extract all volumes from the nested structure + var allVolumes []VolumeInfo + for dcName, dataCenter := range volumeStatus.Volumes.DataCenters { + log.Printf("Processing data center: %s", dcName) + for rackName, rack := range dataCenter { + log.Printf("Processing rack: %s", rackName) + for serverName, volumes := range rack { + log.Printf("Found %d volumes on server %s", len(volumes), serverName) + allVolumes = append(allVolumes, volumes...) + } + } + } + + return allVolumes } type StoredFilePaths struct { diff --git a/docker/admin_integration/ec_test_files.json b/docker/admin_integration/ec_test_files.json index c1cbc0513..fb9181f24 100644 --- a/docker/admin_integration/ec_test_files.json +++ b/docker/admin_integration/ec_test_files.json @@ -1,37 +1,32 @@ { "file_paths": [ - "/ec_test/large_file_1754816105_0.dat", - "/ec_test/large_file_1754816105_1.dat", - "/ec_test/large_file_1754816105_2.dat", - "/ec_test/large_file_1754816105_3.dat", - "/ec_test/large_file_1754816105_4.dat", - "/ec_test/large_file_1754816105_5.dat", - "/ec_test/large_file_1754816105_6.dat", - "/ec_test/large_file_1754816105_7.dat", - "/ec_test/large_file_1754816105_8.dat", - "/ec_test/large_file_1754816105_9.dat", - "/ec_test/large_file_1754816105_10.dat", - "/ec_test/large_file_1754816105_11.dat", - "/ec_test/large_file_1754816105_12.dat", - "/ec_test/large_file_1754816106_13.dat", - "/ec_test/large_file_1754816106_14.dat", - "/ec_test/large_file_1754816106_15.dat", - "/ec_test/large_file_1754816106_16.dat", - "/ec_test/large_file_1754816106_17.dat", - "/ec_test/large_file_1754816106_18.dat", - "/ec_test/large_file_1754816106_19.dat", - "/ec_test/large_file_1754816106_20.dat", - "/ec_test/large_file_1754816106_21.dat", - "/ec_test/large_file_1754816106_22.dat", - "/ec_test/large_file_1754816106_23.dat", - "/ec_test/large_file_1754816106_24.dat", - "/ec_test/large_file_1754816106_25.dat", - "/ec_test/large_file_1754816106_26.dat", - "/ec_test/large_file_1754816106_27.dat", - "/ec_test/large_file_1754816106_28.dat", - "/ec_test/large_file_1754816106_29.dat" + "/ec_test/large_file_1754854356_0.dat", + "/ec_test/large_file_1754854356_1.dat", + "/ec_test/large_file_1754854356_2.dat", + "/ec_test/large_file_1754854356_3.dat", + "/ec_test/large_file_1754854356_4.dat", + "/ec_test/large_file_1754854356_5.dat", + "/ec_test/large_file_1754854356_6.dat", + "/ec_test/large_file_1754854356_7.dat", + "/ec_test/large_file_1754854356_8.dat", + "/ec_test/large_file_1754854356_9.dat", + "/ec_test/large_file_1754854356_10.dat", + "/ec_test/large_file_1754854356_11.dat", + "/ec_test/large_file_1754854356_12.dat", + "/ec_test/large_file_1754854356_13.dat", + "/ec_test/large_file_1754854356_14.dat", + "/ec_test/large_file_1754854356_15.dat", + "/ec_test/large_file_1754854356_16.dat", + "/ec_test/large_file_1754854356_17.dat", + "/ec_test/large_file_1754854357_18.dat", + "/ec_test/large_file_1754854357_19.dat", + "/ec_test/large_file_1754854357_20.dat", + "/ec_test/large_file_1754854357_21.dat", + "/ec_test/large_file_1754854357_22.dat", + "/ec_test/large_file_1754854357_23.dat", + "/ec_test/large_file_1754854357_24.dat" ], - "timestamp": "2025-08-10T08:55:06.363144049Z", - "file_count": 30, + "timestamp": "2025-08-10T19:32:37.127812169Z", + "file_count": 25, "file_size_kb": 3000 } \ No newline at end of file diff --git a/weed/admin/dash/ec_shard_management.go b/weed/admin/dash/ec_shard_management.go index 34574ecdb..556b970bc 100644 --- a/weed/admin/dash/ec_shard_management.go +++ b/weed/admin/dash/ec_shard_management.go @@ -727,6 +727,20 @@ func (s *AdminServer) GetEcVolumeDetails(volumeID uint32, sortBy string, sortOrd serverList = append(serverList, server) } + // Get EC volume health metrics (deletion information) + volumeHealth, err := s.getEcVolumeHealthMetrics(volumeID) + if err != nil { + glog.V(1).Infof("Failed to get EC volume health metrics for volume %d: %v", volumeID, err) + // Don't fail the request, just use default values + volumeHealth = &EcVolumeHealthInfo{ + TotalSize: 0, + DeletedByteCount: 0, + FileCount: 0, + DeleteCount: 0, + GarbageRatio: 0.0, + } + } + data := &EcVolumeDetailsData{ VolumeID: volumeID, Collection: collection, @@ -737,9 +751,159 @@ func (s *AdminServer) GetEcVolumeDetails(volumeID uint32, sortBy string, sortOrd DataCenters: dcList, Servers: serverList, LastUpdated: time.Now(), - SortBy: sortBy, - SortOrder: sortOrder, + + // Volume health metrics (for EC vacuum) + TotalSize: volumeHealth.TotalSize, + DeletedByteCount: volumeHealth.DeletedByteCount, + FileCount: volumeHealth.FileCount, + DeleteCount: volumeHealth.DeleteCount, + GarbageRatio: volumeHealth.GarbageRatio, + + SortBy: sortBy, + SortOrder: sortOrder, } return data, nil } + +// getEcVolumeHealthMetrics retrieves health metrics for an EC volume +func (s *AdminServer) getEcVolumeHealthMetrics(volumeID uint32) (*EcVolumeHealthInfo, error) { + // Get list of servers that have shards for this EC volume + var servers []string + + err := s.WithMasterClient(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 { + // Check if this node has EC shards for our volume + for _, ecShardInfo := range diskInfo.EcShardInfos { + if ecShardInfo.Id == volumeID { + servers = append(servers, node.Id) + goto nextNode // Found shards on this node, move to next node + } + } + } + } + nextNode: + } + } + } + return nil + }) + + if err != nil { + return nil, fmt.Errorf("failed to get topology info: %v", err) + } + + if len(servers) == 0 { + return nil, fmt.Errorf("no servers found with EC shards for volume %d", volumeID) + } + + // Try to get volume file status from servers that have EC shards + // The volume health metrics should be stored with the EC volume metadata + for _, server := range servers { + healthInfo, err := s.getVolumeHealthFromServer(server, volumeID) + if err != nil { + glog.V(2).Infof("Failed to get volume health from server %s for volume %d: %v", server, volumeID, err) + continue // Try next server + } + if healthInfo != nil { + return healthInfo, nil + } + } + + // If we can't get the original metrics, try to calculate from EC shards + return s.calculateHealthFromEcShards(volumeID, servers) +} + +// getVolumeHealthFromServer gets volume health information from a specific server +func (s *AdminServer) getVolumeHealthFromServer(server string, volumeID uint32) (*EcVolumeHealthInfo, error) { + var healthInfo *EcVolumeHealthInfo + + err := s.WithVolumeServerClient(pb.ServerAddress(server), func(client volume_server_pb.VolumeServerClient) error { + // Try to get volume file status (which may include original volume metrics) + resp, err := client.ReadVolumeFileStatus(context.Background(), &volume_server_pb.ReadVolumeFileStatusRequest{ + VolumeId: volumeID, + }) + if err != nil { + return err + } + + // Extract health metrics from volume info + if resp.VolumeInfo != nil { + totalSize := uint64(resp.VolumeInfo.DatFileSize) + if totalSize > 0 { + healthInfo = &EcVolumeHealthInfo{ + TotalSize: totalSize, + DeletedByteCount: 0, // EC volumes don't track deletions in VolumeInfo + FileCount: resp.FileCount, + DeleteCount: 0, // Not available in current API + GarbageRatio: 0.0, + } + } + } + + return nil + }) + + return healthInfo, err +} + +// calculateHealthFromEcShards attempts to calculate health metrics from EC shard information +func (s *AdminServer) calculateHealthFromEcShards(volumeID uint32, servers []string) (*EcVolumeHealthInfo, error) { + var totalShardSize uint64 + shardCount := 0 + + // Get shard sizes from all servers + for _, server := range servers { + err := s.WithVolumeServerClient(pb.ServerAddress(server), func(client volume_server_pb.VolumeServerClient) error { + resp, err := client.VolumeEcShardsInfo(context.Background(), &volume_server_pb.VolumeEcShardsInfoRequest{ + VolumeId: volumeID, + }) + if err != nil { + return err + } + + for _, shardInfo := range resp.EcShardInfos { + totalShardSize += uint64(shardInfo.Size) + shardCount++ + } + + return nil + }) + if err != nil { + glog.V(2).Infof("Failed to get EC shard info from server %s: %v", server, err) + } + } + + if shardCount == 0 { + return nil, fmt.Errorf("no EC shard information found for volume %d", volumeID) + } + + // For EC volumes, we can estimate the original size from the data shards + // EC uses 10 data shards + 4 parity shards = 14 total + // The original volume size is approximately the sum of the 10 data shards + dataShardCount := 10 // erasure_coding.DataShardsCount + estimatedOriginalSize := totalShardSize + + if shardCount >= dataShardCount { + // If we have info from data shards, estimate better + avgShardSize := totalShardSize / uint64(shardCount) + estimatedOriginalSize = avgShardSize * uint64(dataShardCount) + } + + return &EcVolumeHealthInfo{ + TotalSize: estimatedOriginalSize, + DeletedByteCount: 0, // Cannot determine from EC shards alone + FileCount: 0, // Cannot determine from EC shards alone + DeleteCount: 0, // Cannot determine from EC shards alone + GarbageRatio: 0.0, + }, nil +} diff --git a/weed/admin/dash/types.go b/weed/admin/dash/types.go index 18c46a48d..676d51e89 100644 --- a/weed/admin/dash/types.go +++ b/weed/admin/dash/types.go @@ -224,6 +224,13 @@ type EcVolumeDetailsData struct { Servers []string `json:"servers"` LastUpdated time.Time `json:"last_updated"` + // Volume health metrics (for EC vacuum) + TotalSize uint64 `json:"total_size"` // Total volume size before EC + DeletedByteCount uint64 `json:"deleted_byte_count"` // Deleted bytes count + FileCount uint64 `json:"file_count"` // Total file count + DeleteCount uint64 `json:"delete_count"` // Deleted file count + GarbageRatio float64 `json:"garbage_ratio"` // Deletion ratio (0.0-1.0) + // Sorting SortBy string `json:"sort_by"` SortOrder string `json:"sort_order"` @@ -237,6 +244,15 @@ type VolumeDetailsData struct { LastUpdated time.Time `json:"last_updated"` } +// EcVolumeHealthInfo represents health metrics for an EC volume +type EcVolumeHealthInfo struct { + TotalSize uint64 `json:"total_size"` // Original volume size before EC + DeletedByteCount uint64 `json:"deleted_byte_count"` // Deleted bytes count + FileCount uint64 `json:"file_count"` // Total file count + DeleteCount uint64 `json:"delete_count"` // Deleted file count + GarbageRatio float64 `json:"garbage_ratio"` // Deletion ratio (0.0-1.0) +} + // Collection management structures type CollectionInfo struct { Name string `json:"name"` diff --git a/weed/admin/view/app/ec_volume_details.templ b/weed/admin/view/app/ec_volume_details.templ index caf506d0f..1c69b489d 100644 --- a/weed/admin/view/app/ec_volume_details.templ +++ b/weed/admin/view/app/ec_volume_details.templ @@ -33,7 +33,7 @@ templ EcVolumeDetails(data dash.EcVolumeDetailsData) {
-
+
@@ -111,7 +111,86 @@ templ EcVolumeDetails(data dash.EcVolumeDetailsData) {
-
+
+
+
+
+ Volume Health +
+
+
+ + if data.TotalSize > 0 { +
+
+
+
+ {bytesToHumanReadableUint64(data.TotalSize - data.DeletedByteCount)} +
+ Active Bytes +
+
+
+
+
+ {bytesToHumanReadableUint64(data.DeletedByteCount)} +
+ Deleted Bytes +
+
+
+ + +
+
+
+
+ {fmt.Sprintf("%d", data.FileCount - data.DeleteCount)} +
+ Active Files +
+
+
+
+
+ {fmt.Sprintf("%d", data.DeleteCount)} +
+ Deleted Files +
+
+
+ + +
+
+
+
+ {fmt.Sprintf("%.1f%%", data.GarbageRatio * 100)} +
+ Garbage Ratio + if data.GarbageRatio >= 0.3 { +
+ + EC Vacuum Candidate + +
+ } +
+
+
+ } else { +
+ +
Volume health metrics not available
+ This may be normal for newly created EC volumes +
+ } +
+
+
+ + +
@@ -310,4 +389,16 @@ func bytesToHumanReadableUint64(bytes uint64) string { exp++ } return fmt.Sprintf("%.1f%cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} + +// Helper function to get color for garbage ratio display +func getGarbageRatioColor(ratio float64) string { + if ratio >= 0.5 { + return "#dc3545" // Red for high garbage ratio + } else if ratio >= 0.3 { + return "#fd7e14" // Orange for medium garbage ratio + } else if ratio >= 0.1 { + return "#ffc107" // Yellow for low garbage ratio + } + return "#28a745" // Green for very low garbage ratio } \ No newline at end of file diff --git a/weed/admin/view/app/ec_volume_details_templ.go b/weed/admin/view/app/ec_volume_details_templ.go index e96514ce7..5de7b75c6 100644 --- a/weed/admin/view/app/ec_volume_details_templ.go +++ b/weed/admin/view/app/ec_volume_details_templ.go @@ -47,7 +47,7 @@ func EcVolumeDetails(data dash.EcVolumeDetailsData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Volume Information
Volume ID:") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Volume Information
Volume ID:") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -223,319 +223,422 @@ func EcVolumeDetails(data dash.EcVolumeDetailsData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Shard Distribution

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

Volume Health
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalShards)) + if data.TotalSize > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(bytesToHumanReadableUint64(data.TotalSize - data.DeletedByteCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 128, Col: 107} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
Active Bytes
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(bytesToHumanReadableUint64(data.DeletedByteCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 136, Col: 90} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Deleted Bytes
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.FileCount-data.DeleteCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 148, Col: 93} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
Active Files
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.DeleteCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 156, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
Deleted Files
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%%", data.GarbageRatio*100)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 168, Col: 87} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
Garbage Ratio ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.GarbageRatio >= 0.3 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
EC Vacuum Candidate
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
Volume health metrics not available
This may be normal for newly created EC volumes
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
Shard Distribution

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalShards)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 125, Col: 98} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 204, Col: 98} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

Total Shards

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

Total Shards

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.DataCenters))) + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.DataCenters))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 131, Col: 103} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 210, Col: 103} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

Data Centers

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

Data Centers

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Servers))) + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Servers))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 137, Col: 96} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 216, Col: 96} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

Servers
Present Shards:
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "Servers
Present Shards:
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, shard := range data.Shards { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", shard.ShardID)) + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", shard.ShardID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 148, Col: 108} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 227, Col: 108} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(data.MissingShards) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
Missing Shards:
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
Missing Shards:
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, shardID := range data.MissingShards { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", shardID)) + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", shardID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 155, Col: 108} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 234, Col: 108} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
Shard Details
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
Shard Details
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(data.Shards) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
Shard ID ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, shard := range data.Shards { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\" target=\"_blank\" class=\"btn btn-sm btn-primary\">Volume Server") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
Shard ID ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.SortBy == "shard_id" { if data.SortOrder == "asc" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "Server ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "Server ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.SortBy == "server" { if data.SortOrder == "asc" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "Data Center ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "Data Center ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.SortBy == "data_center" { if data.SortOrder == "asc" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "Rack ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "Rack ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.SortBy == "rack" { if data.SortOrder == "asc" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "Disk TypeShard SizeActions
Disk TypeShard SizeActions
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", shard.ShardID)) + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", shard.ShardID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 243, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 322, Col: 110} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\" class=\"text-primary text-decoration-none\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(shard.Server) + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(shard.Server) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 247, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 326, Col: 81} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(shard.DataCenter) + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(shard.DataCenter) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 251, Col: 103} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 330, Col: 103} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var20 string - templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(shard.Rack) + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(shard.Rack) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 254, Col: 99} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 333, Col: 99} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var21 string - templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(shard.DiskType) + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(shard.DiskType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 257, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 336, Col: 83} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(bytesToHumanReadableUint64(shard.Size)) + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(bytesToHumanReadableUint64(shard.Size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 260, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/ec_volume_details.templ`, Line: 339, Col: 110} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "Volume Server
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
No EC shards found

This volume may not be EC encoded yet.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
No EC shards found

This volume may not be EC encoded yet.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -557,4 +660,16 @@ func bytesToHumanReadableUint64(bytes uint64) string { return fmt.Sprintf("%.1f%cB", float64(bytes)/float64(div), "KMGTPE"[exp]) } +// Helper function to get color for garbage ratio display +func getGarbageRatioColor(ratio float64) string { + if ratio >= 0.5 { + return "#dc3545" // Red for high garbage ratio + } else if ratio >= 0.3 { + return "#fd7e14" // Orange for medium garbage ratio + } else if ratio >= 0.1 { + return "#ffc107" // Yellow for low garbage ratio + } + return "#28a745" // Green for very low garbage ratio +} + var _ = templruntime.GeneratedTemplate