github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/data-usage.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "errors" 23 "strings" 24 "time" 25 26 jsoniter "github.com/json-iterator/go" 27 "github.com/minio/minio/internal/cachevalue" 28 "github.com/minio/minio/internal/logger" 29 ) 30 31 const ( 32 dataUsageRoot = SlashSeparator 33 dataUsageBucket = minioMetaBucket + SlashSeparator + bucketMetaPrefix 34 35 dataUsageObjName = ".usage.json" 36 dataUsageObjNamePath = bucketMetaPrefix + SlashSeparator + dataUsageObjName 37 dataUsageBloomName = ".bloomcycle.bin" 38 dataUsageBloomNamePath = bucketMetaPrefix + SlashSeparator + dataUsageBloomName 39 40 backgroundHealInfoPath = bucketMetaPrefix + SlashSeparator + ".background-heal.json" 41 42 dataUsageCacheName = ".usage-cache.bin" 43 ) 44 45 // storeDataUsageInBackend will store all objects sent on the dui channel until closed. 46 func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) { 47 attempts := 1 48 for dataUsageInfo := range dui { 49 json := jsoniter.ConfigCompatibleWithStandardLibrary 50 dataUsageJSON, err := json.Marshal(dataUsageInfo) 51 if err != nil { 52 logger.LogIf(ctx, err) 53 continue 54 } 55 if attempts > 10 { 56 saveConfig(ctx, objAPI, dataUsageObjNamePath+".bkp", dataUsageJSON) // Save a backup every 10th update. 57 attempts = 1 58 } 59 if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil { 60 logger.LogOnceIf(ctx, err, dataUsageObjNamePath) 61 } 62 attempts++ 63 } 64 } 65 66 var prefixUsageCache = cachevalue.New[map[string]uint64]() 67 68 // loadPrefixUsageFromBackend returns prefix usages found in passed buckets 69 // 70 // e.g.: /testbucket/prefix => 355601334 71 func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket string) (map[string]uint64, error) { 72 z, ok := objAPI.(*erasureServerPools) 73 if !ok { 74 // Prefix usage is empty 75 return map[string]uint64{}, nil 76 } 77 78 cache := dataUsageCache{} 79 80 prefixUsageCache.InitOnce(30*time.Second, 81 // No need to fail upon Update() error, fallback to old value. 82 cachevalue.Opts{ReturnLastGood: true, NoWait: true}, 83 func() (map[string]uint64, error) { 84 m := make(map[string]uint64) 85 for _, pool := range z.serverPools { 86 for _, er := range pool.sets { 87 // Load bucket usage prefixes 88 ctx, done := context.WithTimeout(context.Background(), 2*time.Second) 89 ok := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName) == nil 90 done() 91 if ok { 92 root := cache.find(bucket) 93 if root == nil { 94 // We dont have usage information for this bucket in this 95 // set, go to the next set 96 continue 97 } 98 99 for id, usageInfo := range cache.flattenChildrens(*root) { 100 prefix := decodeDirObject(strings.TrimPrefix(id, bucket+slashSeparator)) 101 // decodeDirObject to avoid any __XLDIR__ objects 102 m[prefix] += uint64(usageInfo.Size) 103 } 104 } 105 } 106 } 107 return m, nil 108 }, 109 ) 110 111 return prefixUsageCache.Get() 112 } 113 114 func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsageInfo, error) { 115 buf, err := readConfig(ctx, objAPI, dataUsageObjNamePath) 116 if err != nil { 117 buf, err = readConfig(ctx, objAPI, dataUsageObjNamePath+".bkp") 118 if err != nil { 119 if errors.Is(err, errConfigNotFound) { 120 return DataUsageInfo{}, nil 121 } 122 return DataUsageInfo{}, toObjectErr(err, minioMetaBucket, dataUsageObjNamePath) 123 } 124 } 125 126 var dataUsageInfo DataUsageInfo 127 json := jsoniter.ConfigCompatibleWithStandardLibrary 128 if err = json.Unmarshal(buf, &dataUsageInfo); err != nil { 129 return DataUsageInfo{}, err 130 } 131 // For forward compatibility reasons, we need to add this code. 132 if len(dataUsageInfo.BucketsUsage) == 0 { 133 dataUsageInfo.BucketsUsage = make(map[string]BucketUsageInfo, len(dataUsageInfo.BucketSizes)) 134 for bucket, size := range dataUsageInfo.BucketSizes { 135 dataUsageInfo.BucketsUsage[bucket] = BucketUsageInfo{Size: size} 136 } 137 } 138 139 // For backward compatibility reasons, we need to add this code. 140 if len(dataUsageInfo.BucketSizes) == 0 { 141 dataUsageInfo.BucketSizes = make(map[string]uint64, len(dataUsageInfo.BucketsUsage)) 142 for bucket, bui := range dataUsageInfo.BucketsUsage { 143 dataUsageInfo.BucketSizes[bucket] = bui.Size 144 } 145 } 146 // For forward compatibility reasons, we need to add this code. 147 for bucket, bui := range dataUsageInfo.BucketsUsage { 148 if bui.ReplicatedSizeV1 > 0 || bui.ReplicationFailedCountV1 > 0 || 149 bui.ReplicationFailedSizeV1 > 0 || bui.ReplicationPendingCountV1 > 0 { 150 cfg, _ := getReplicationConfig(GlobalContext, bucket) 151 if cfg != nil && cfg.RoleArn != "" { 152 if dataUsageInfo.ReplicationInfo == nil { 153 dataUsageInfo.ReplicationInfo = make(map[string]BucketTargetUsageInfo) 154 } 155 dataUsageInfo.ReplicationInfo[cfg.RoleArn] = BucketTargetUsageInfo{ 156 ReplicationFailedSize: bui.ReplicationFailedSizeV1, 157 ReplicationFailedCount: bui.ReplicationFailedCountV1, 158 ReplicatedSize: bui.ReplicatedSizeV1, 159 ReplicationPendingCount: bui.ReplicationPendingCountV1, 160 ReplicationPendingSize: bui.ReplicationPendingSizeV1, 161 } 162 } 163 } 164 } 165 return dataUsageInfo, nil 166 }