github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/disk/stat_linux.go (about) 1 //go:build linux && !s390x && !arm && !386 2 // +build linux,!s390x,!arm,!386 3 4 // Copyright (c) 2015-2023 MinIO, Inc. 5 // 6 // This file is part of MinIO Object Storage stack 7 // 8 // This program is free software: you can redistribute it and/or modify 9 // it under the terms of the GNU Affero General Public License as published by 10 // the Free Software Foundation, either version 3 of the License, or 11 // (at your option) any later version. 12 // 13 // This program is distributed in the hope that it will be useful 14 // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 // GNU Affero General Public License for more details. 17 // 18 // You should have received a copy of the GNU Affero General Public License 19 // along with this program. If not, see <http://www.gnu.org/licenses/>. 20 21 package disk 22 23 import ( 24 "bufio" 25 "errors" 26 "fmt" 27 "io" 28 "os" 29 "path/filepath" 30 "strconv" 31 "strings" 32 "syscall" 33 34 "github.com/prometheus/procfs/blockdevice" 35 "golang.org/x/sys/unix" 36 ) 37 38 // GetInfo returns total and free bytes available in a directory, e.g. `/`. 39 func GetInfo(path string, firstTime bool) (info Info, err error) { 40 s := syscall.Statfs_t{} 41 err = syscall.Statfs(path, &s) 42 if err != nil { 43 return Info{}, err 44 } 45 reservedBlocks := s.Bfree - s.Bavail 46 info = Info{ 47 Total: uint64(s.Frsize) * (s.Blocks - reservedBlocks), 48 Free: uint64(s.Frsize) * s.Bavail, 49 Files: s.Files, 50 Ffree: s.Ffree, 51 //nolint:unconvert 52 FSType: getFSType(int64(s.Type)), 53 } 54 55 st := syscall.Stat_t{} 56 err = syscall.Stat(path, &st) 57 if err != nil { 58 return Info{}, err 59 } 60 //nolint:unconvert 61 devID := uint64(st.Dev) // Needed to support multiple GOARCHs 62 info.Major = unix.Major(devID) 63 info.Minor = unix.Minor(devID) 64 65 // Check for overflows. 66 // https://github.com/minio/minio/issues/8035 67 // XFS can show wrong values at times error out 68 // in such scenarios. 69 if info.Free > info.Total { 70 return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path) 71 } 72 info.Used = info.Total - info.Free 73 74 if firstTime { 75 bfs, err := blockdevice.NewDefaultFS() 76 if err == nil { 77 devName := "" 78 diskstats, _ := bfs.ProcDiskstats() 79 for _, dstat := range diskstats { 80 // ignore all loop devices 81 if strings.HasPrefix(dstat.DeviceName, "loop") { 82 continue 83 } 84 if dstat.MajorNumber == info.Major && dstat.MinorNumber == info.Minor { 85 devName = dstat.DeviceName 86 break 87 } 88 } 89 if devName != "" { 90 info.Name = devName 91 qst, err := bfs.SysBlockDeviceQueueStats(devName) 92 if err != nil { // Mostly not found error 93 // Check if there is a parent device: 94 // e.g. if the mount is based on /dev/nvme0n1p1, let's calculate the 95 // real device name (nvme0n1) to get its sysfs information 96 parentDevPath, e := os.Readlink("/sys/class/block/" + devName) 97 if e == nil { 98 parentDev := filepath.Base(filepath.Dir(parentDevPath)) 99 qst, err = bfs.SysBlockDeviceQueueStats(parentDev) 100 } 101 } 102 if err == nil { 103 info.NRRequests = qst.NRRequests 104 rot := qst.Rotational == 1 // Rotational is '1' if the device is HDD 105 info.Rotational = &rot 106 } 107 } 108 } 109 } 110 111 return info, nil 112 } 113 114 // GetDriveStats returns IO stats of the drive by its major:minor 115 func GetDriveStats(major, minor uint32) (iostats IOStats, err error) { 116 return readDriveStats(fmt.Sprintf("/sys/dev/block/%v:%v/stat", major, minor)) 117 } 118 119 func readDriveStats(statsFile string) (iostats IOStats, err error) { 120 stats, err := readStat(statsFile) 121 if err != nil { 122 return IOStats{}, err 123 } 124 if len(stats) < 11 { 125 return IOStats{}, fmt.Errorf("found invalid format while reading %v", statsFile) 126 } 127 // refer https://www.kernel.org/doc/Documentation/block/stat.txt 128 iostats = IOStats{ 129 ReadIOs: stats[0], 130 ReadMerges: stats[1], 131 ReadSectors: stats[2], 132 ReadTicks: stats[3], 133 WriteIOs: stats[4], 134 WriteMerges: stats[5], 135 WriteSectors: stats[6], 136 WriteTicks: stats[7], 137 CurrentIOs: stats[8], 138 TotalTicks: stats[9], 139 ReqTicks: stats[10], 140 } 141 // as per the doc, only 11 fields are guaranteed 142 // only set if available 143 if len(stats) > 14 { 144 iostats.DiscardIOs = stats[11] 145 iostats.DiscardMerges = stats[12] 146 iostats.DiscardSectors = stats[13] 147 iostats.DiscardTicks = stats[14] 148 } 149 return 150 } 151 152 func readStat(fileName string) (stats []uint64, err error) { 153 file, err := os.Open(fileName) 154 if err != nil { 155 return nil, err 156 } 157 defer file.Close() 158 159 s, err := bufio.NewReader(file).ReadString('\n') 160 if err != nil && !errors.Is(err, io.EOF) { 161 return nil, err 162 } 163 statLine := strings.TrimSpace(s) 164 for _, token := range strings.Fields(statLine) { 165 ui64, err := strconv.ParseUint(token, 10, 64) 166 if err != nil { 167 return nil, err 168 } 169 stats = append(stats, ui64) 170 } 171 172 return stats, nil 173 }