github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ios/diskstats_linux.go (about) 1 // Package ios is a collection of interfaces to the local storage subsystem; 2 // the package includes OS-dependent implementations for those interfaces. 3 /* 4 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package ios 7 8 import ( 9 "bufio" 10 "fmt" 11 "os" 12 "regexp" 13 "strconv" 14 "strings" 15 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/debug" 18 ) 19 20 // Based on: 21 // - https://www.kernel.org/doc/Documentation/iostats.txt 22 // - https://www.kernel.org/doc/Documentation/block/stat.txt 23 type blockStats struct { 24 readComplete int64 // 1 - # of reads completed 25 readMerged int64 // 2 - # of reads merged 26 readSectors int64 // 3 - # of sectors read 27 readMs int64 // 4 - # ms spent reading 28 writeComplete int64 // 5 - # writes completed 29 writeMerged int64 // 6 - # writes merged 30 writeSectors int64 // 7 - # of sectors written 31 writeMs int64 // 8 - # of milliseconds spent writing 32 ioPending int64 // 9 - # of I/Os currently in progress 33 ioMs int64 // 10 - # of milliseconds spent doing I/Os 34 ioMsWeighted int64 // 11 - weighted # of milliseconds spent doing I/Os 35 // 12 - 15: discard I/Os, discard merges, discard sectors, discard ticks 36 // 16, 17: flash I/Os, flash ticks, as per https://github.com/sysstat/sysstat/blob/master/iostat.c 37 } 38 39 type allBlockStats map[string]*blockStats 40 41 // The "sectors" in question are the standard UNIX 512-byte sectors, not any device- or filesystem-specific block size 42 // (from https://www.kernel.org/doc/Documentation/block/stat.txt) 43 const sectorSize = int64(512) 44 45 var ( 46 regex = regexp.MustCompile(`nvme(\d+)n(\d+)`) 47 cregex = regexp.MustCompile(`nvme(\d+)c(\d+)n(\d+)`) 48 ) 49 50 func readStats(disks, sysfnames cos.StrKVs, all allBlockStats) { 51 for disk := range disks { 52 ds, ok := all[disk] 53 debug.Assert(ok, disk) 54 _ = _read(sysfnames[disk], ds) 55 } 56 } 57 58 // https://www.kernel.org/doc/Documentation/block/stat.txt 59 func _read(sysfn string, ds *blockStats) bool { 60 file, err := os.Open(sysfn) 61 if err != nil { 62 return false 63 } 64 scanner := bufio.NewScanner(file) 65 scanner.Scan() 66 fields := strings.Fields(scanner.Text()) 67 68 _ = file.Close() 69 if len(fields) < 11 { 70 return false 71 } 72 *ds = blockStats{ 73 _exI64(fields[0]), 74 _exI64(fields[1]), 75 _exI64(fields[2]), 76 _exI64(fields[3]), 77 _exI64(fields[4]), 78 _exI64(fields[5]), 79 _exI64(fields[6]), 80 _exI64(fields[7]), 81 _exI64(fields[8]), 82 _exI64(fields[9]), 83 _exI64(fields[10]), 84 } 85 return true 86 } 87 88 func _exI64(field string) int64 { 89 val, err := strconv.ParseInt(field, 10, 64) 90 debug.AssertNoErr(err) 91 return val 92 } 93 94 func (ds *blockStats) Reads() int64 { return ds.readComplete } 95 func (ds *blockStats) ReadBytes() int64 { return ds.readSectors * sectorSize } 96 func (ds *blockStats) Writes() int64 { return ds.writeComplete } 97 func (ds *blockStats) WriteBytes() int64 { return ds.writeSectors * sectorSize } 98 func (ds *blockStats) IOMs() int64 { return ds.ioMs } 99 func (ds *blockStats) WriteMs() int64 { return ds.writeMs } 100 func (ds *blockStats) ReadMs() int64 { return ds.readMs } 101 102 // NVMe multipathing 103 // * nvmeInN: instance I namespace N 104 // * nvmeIcCnN: instance I controller C namespace N 105 106 // instance-controller-namespace (icn): 107 // given "nvmeInN" return the corresponding multipath "nvmeIcCnN" (same instance, same namespace) 108 func icn(disk, dir string) (cdisk string, err error) { 109 if !strings.HasPrefix(disk, "nvme") { 110 return 111 } 112 a := regex.FindStringSubmatch(disk) 113 if len(a) < 3 { 114 return 115 } 116 dentries, errN := os.ReadDir(dir) 117 if errN != nil { 118 return "", fmt.Errorf("%q does not parse as NVMe icn", dir) 119 } 120 for _, d := range dentries { 121 name := d.Name() 122 if !strings.HasPrefix(name, "nvme") { 123 continue 124 } 125 b := cregex.FindStringSubmatch(name) 126 if len(b) < 4 { 127 continue 128 } 129 if a[1] == b[1] && a[2] == b[3] { 130 cdisk = name 131 break 132 } 133 } 134 return 135 } 136 137 func icnPath(dir, cdir, mountpath string) bool { 138 var ( 139 stats, cstats blockStats 140 ) 141 ok := _read(dir, &stats) 142 cok := _read(cdir, &cstats) 143 if !cok { 144 return false 145 } 146 if !ok { 147 return true 148 } 149 // first, an easy check 150 if stats.readComplete == 0 && stats.writeComplete == 0 && (cstats.readComplete > 0 || cstats.writeComplete > 0) { 151 return true 152 } 153 154 // write at the root of the mountpath and check whether (the alternative) stats incremented 155 fh, err := os.CreateTemp(mountpath, "") 156 if err != nil { 157 debug.Assert(err == nil, mountpath, err) 158 return false 159 } 160 fqn := fh.Name() 161 fh.Close() 162 err = os.Remove(fqn) 163 debug.AssertNoErr(err) 164 165 var ( 166 stats2, cstats2 blockStats 167 ) 168 ok = _read(dir, &stats2) 169 cok = _read(cdir, &cstats2) 170 debug.Assert(ok && cok) 171 return stats.writeComplete == stats2.writeComplete && cstats.writeComplete < cstats2.writeComplete 172 }