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  }