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  }