github.com/dennwc/btrfs@v0.0.0-20221026161108-3097362dc072/usage.go (about) 1 package btrfs 2 3 import ( 4 "os" 5 "sort" 6 "syscall" 7 ) 8 9 func cmpChunkBlockGroup(f1, f2 blockGroup) int { 10 var mask blockGroup 11 12 if (f1 & _BTRFS_BLOCK_GROUP_TYPE_MASK) == 13 (f2 & _BTRFS_BLOCK_GROUP_TYPE_MASK) { 14 mask = _BTRFS_BLOCK_GROUP_PROFILE_MASK 15 } else if f2&blockGroupSystem != 0 { 16 return -1 17 } else if f1&blockGroupSystem != 0 { 18 return +1 19 } else { 20 mask = _BTRFS_BLOCK_GROUP_TYPE_MASK 21 } 22 23 if (f1 & mask) > (f2 & mask) { 24 return +1 25 } else if (f1 & mask) < (f2 & mask) { 26 return -1 27 } else { 28 return 0 29 } 30 } 31 32 type spaceInfoByBlockGroup []spaceInfo 33 34 func (a spaceInfoByBlockGroup) Len() int { return len(a) } 35 func (a spaceInfoByBlockGroup) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 36 func (a spaceInfoByBlockGroup) Less(i, j int) bool { 37 return cmpChunkBlockGroup(blockGroup(a[i].Flags), blockGroup(a[j].Flags)) < 0 38 } 39 40 type UsageInfo struct { 41 Total uint64 42 TotalUnused uint64 43 TotalUsed uint64 44 TotalChunks uint64 45 46 FreeEstimated uint64 47 FreeMin uint64 48 49 LogicalDataChunks uint64 50 RawDataChunks uint64 51 RawDataUsed uint64 52 53 LogicalMetaChunks uint64 54 RawMetaChunks uint64 55 RawMetaUsed uint64 56 57 SystemUsed uint64 58 SystemChunks uint64 59 60 DataRatio float64 61 MetadataRatio float64 62 63 GlobalReserve uint64 64 GlobalReserveUsed uint64 65 } 66 67 const minUnallocatedThreshold = 16 * 1024 * 1024 68 69 func spaceUsage(f *os.File) (UsageInfo, error) { 70 info, err := iocFsInfo(f) 71 if err != nil { 72 return UsageInfo{}, err 73 } 74 var u UsageInfo 75 for i := uint64(0); i <= info.max_id; i++ { 76 dev, err := iocDevInfo(f, i, UUID{}) 77 if err == syscall.ENODEV { 78 continue 79 } else if err != nil { 80 return UsageInfo{}, err 81 } 82 u.Total += dev.total_bytes 83 } 84 85 spaces, err := iocSpaceInfo(f) 86 if err != nil { 87 return UsageInfo{}, err 88 } 89 sort.Sort(spaceInfoByBlockGroup(spaces)) 90 var ( 91 maxDataRatio int = 1 92 mixed bool 93 ) 94 for _, s := range spaces { 95 ratio := 1 96 bg := s.Flags.BlockGroup() 97 switch { 98 case bg&blockGroupRaid0 != 0: 99 ratio = 1 100 case bg&blockGroupRaid1 != 0: 101 ratio = 2 102 case bg&blockGroupRaid5 != 0: 103 ratio = 0 104 case bg&blockGroupRaid6 != 0: 105 ratio = 0 106 case bg&blockGroupDup != 0: 107 ratio = 2 108 case bg&blockGroupRaid10 != 0: 109 ratio = 2 110 } 111 if ratio > maxDataRatio { 112 maxDataRatio = ratio 113 } 114 if bg&spaceInfoGlobalRsv != 0 { 115 u.GlobalReserve = s.TotalBytes 116 u.GlobalReserveUsed = s.UsedBytes 117 } 118 if bg&(blockGroupData|blockGroupMetadata) == (blockGroupData | blockGroupMetadata) { 119 mixed = true 120 } 121 if bg&blockGroupData != 0 { 122 u.RawDataUsed += s.UsedBytes * uint64(ratio) 123 u.RawDataChunks += s.TotalBytes * uint64(ratio) 124 u.LogicalDataChunks += s.TotalBytes 125 } 126 if bg&blockGroupMetadata != 0 { 127 u.RawMetaUsed += s.UsedBytes * uint64(ratio) 128 u.RawMetaChunks += s.TotalBytes * uint64(ratio) 129 u.LogicalMetaChunks += s.TotalBytes 130 } 131 if bg&blockGroupSystem != 0 { 132 u.SystemUsed += s.UsedBytes * uint64(ratio) 133 u.SystemChunks += s.TotalBytes * uint64(ratio) 134 } 135 } 136 u.TotalChunks = u.RawDataChunks + u.SystemChunks 137 u.TotalUsed = u.RawDataUsed + u.SystemUsed 138 if !mixed { 139 u.TotalChunks += u.RawMetaChunks 140 u.TotalUsed += u.RawMetaUsed 141 } 142 u.TotalUnused = u.Total - u.TotalChunks 143 144 u.DataRatio = float64(u.RawDataChunks) / float64(u.LogicalDataChunks) 145 if mixed { 146 u.MetadataRatio = u.DataRatio 147 } else { 148 u.MetadataRatio = float64(u.RawMetaChunks) / float64(u.LogicalMetaChunks) 149 } 150 151 // We're able to fill at least DATA for the unused space 152 // 153 // With mixed raid levels, this gives a rough estimate but more 154 // accurate than just counting the logical free space 155 // (l_data_chunks - l_data_used) 156 // 157 // In non-mixed case there's no difference. 158 u.FreeEstimated = uint64(float64(u.RawDataChunks-u.RawDataUsed) / u.DataRatio) 159 160 // For mixed-bg the metadata are left out in calculations thus global 161 // reserve would be lost. Part of it could be permanently allocated, 162 // we have to subtract the used bytes so we don't go under zero free. 163 if mixed { 164 u.FreeEstimated -= u.GlobalReserve - u.GlobalReserveUsed 165 } 166 u.FreeMin = u.FreeEstimated 167 168 // Chop unallocatable space 169 // FIXME: must be applied per device 170 if u.TotalUnused >= minUnallocatedThreshold { 171 u.FreeEstimated += uint64(float64(u.TotalUnused) / u.DataRatio) 172 // Match the calculation of 'df', use the highest raid ratio 173 u.FreeMin += u.TotalUnused / uint64(maxDataRatio) 174 } 175 return u, nil 176 }