pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/system/info_fs_linux.go (about)

     1  package system
     2  
     3  // ////////////////////////////////////////////////////////////////////////////////// //
     4  //                                                                                    //
     5  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     6  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     7  //                                                                                    //
     8  // ////////////////////////////////////////////////////////////////////////////////// //
     9  
    10  import (
    11  	"bufio"
    12  	"errors"
    13  	"path/filepath"
    14  	"strconv"
    15  	"syscall"
    16  	"time"
    17  
    18  	"pkg.re/essentialkaos/ek.v12/strutil"
    19  )
    20  
    21  // ////////////////////////////////////////////////////////////////////////////////// //
    22  
    23  // Path to file with disk info in procfs
    24  var procDiskStatsFile = "/proc/diskstats"
    25  
    26  // Path to mtab file
    27  var mtabFile = "/etc/mtab"
    28  
    29  // ////////////////////////////////////////////////////////////////////////////////// //
    30  
    31  // GetFSUsage returns info about mounted filesystems
    32  func GetFSUsage() (map[string]*FSUsage, error) {
    33  	s, closer, err := getFileScanner(mtabFile)
    34  
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	defer closer()
    40  
    41  	return parseFSInfo(s, true)
    42  }
    43  
    44  // GetIOStats returns IO statistics as map device -> statistics
    45  func GetIOStats() (map[string]*IOStats, error) {
    46  	s, closer, err := getFileScanner(procDiskStatsFile)
    47  
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	defer closer()
    53  
    54  	return parseIOStats(s)
    55  }
    56  
    57  // GetIOUtil returns slice (device -> utilization) with IO utilization
    58  func GetIOUtil(duration time.Duration) (map[string]float64, error) {
    59  	io1, err := GetIOStats()
    60  
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	time.Sleep(duration)
    66  
    67  	io2, err := GetIOStats()
    68  
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	return CalculateIOUtil(io1, io2, duration), nil
    74  }
    75  
    76  // CalculateIOUtil calculates IO utilization for all devices
    77  func CalculateIOUtil(io1, io2 map[string]*IOStats, duration time.Duration) map[string]float64 {
    78  	if io1 == nil || io2 == nil {
    79  		return nil
    80  	}
    81  
    82  	result := make(map[string]float64)
    83  
    84  	// convert duration to jiffies
    85  	itv := uint64(duration / (time.Millisecond * 10))
    86  
    87  	for device := range io1 {
    88  		if io1[device] == nil || io2[device] == nil {
    89  			continue
    90  		}
    91  
    92  		util := float64(io2[device].IOMs-io1[device].IOMs) / float64(itv) * getHZ()
    93  
    94  		util /= 10.0
    95  
    96  		if util > 100.0 {
    97  			util = 100.0
    98  		}
    99  
   100  		result[device] = util
   101  	}
   102  
   103  	return result
   104  }
   105  
   106  // ////////////////////////////////////////////////////////////////////////////////// //
   107  
   108  // codebeat:disable[LOC,ABC,CYCLO]
   109  
   110  // parseIOStats parses IO stats data
   111  func parseIOStats(s *bufio.Scanner) (map[string]*IOStats, error) {
   112  	var err error
   113  
   114  	iostats := make(map[string]*IOStats)
   115  
   116  	for s.Scan() {
   117  		text := s.Text()
   118  		device := strutil.ReadField(text, 2, true)
   119  
   120  		if len(device) > 3 {
   121  			if device[:3] == "ram" || device[:3] == "loo" {
   122  				continue
   123  			}
   124  		}
   125  
   126  		ios := &IOStats{}
   127  
   128  		ios.ReadComplete, err = strconv.ParseUint(strutil.ReadField(text, 3, true), 10, 64)
   129  
   130  		if err != nil {
   131  			return nil, errors.New("Can't parse field 3 as unsigned integer in " + procDiskStatsFile)
   132  		}
   133  
   134  		ios.ReadMerged, err = strconv.ParseUint(strutil.ReadField(text, 4, true), 10, 64)
   135  
   136  		if err != nil {
   137  			return nil, errors.New("Can't parse field 4 as unsigned integer in " + procDiskStatsFile)
   138  		}
   139  
   140  		ios.ReadSectors, err = strconv.ParseUint(strutil.ReadField(text, 5, true), 10, 64)
   141  
   142  		if err != nil {
   143  			return nil, errors.New("Can't parse field 5 as unsigned integer in " + procDiskStatsFile)
   144  		}
   145  
   146  		ios.ReadMs, err = strconv.ParseUint(strutil.ReadField(text, 6, true), 10, 64)
   147  
   148  		if err != nil {
   149  			return nil, errors.New("Can't parse field 6 as unsigned integer in " + procDiskStatsFile)
   150  		}
   151  
   152  		ios.WriteComplete, err = strconv.ParseUint(strutil.ReadField(text, 7, true), 10, 64)
   153  
   154  		if err != nil {
   155  			return nil, errors.New("Can't parse field 7 as unsigned integer in " + procDiskStatsFile)
   156  		}
   157  
   158  		ios.WriteMerged, err = strconv.ParseUint(strutil.ReadField(text, 8, true), 10, 64)
   159  
   160  		if err != nil {
   161  			return nil, errors.New("Can't parse field 8 as unsigned integer in " + procDiskStatsFile)
   162  		}
   163  
   164  		ios.WriteSectors, err = strconv.ParseUint(strutil.ReadField(text, 9, true), 10, 64)
   165  
   166  		if err != nil {
   167  			return nil, errors.New("Can't parse field 9 as unsigned integer in " + procDiskStatsFile)
   168  		}
   169  
   170  		ios.WriteMs, err = strconv.ParseUint(strutil.ReadField(text, 10, true), 10, 64)
   171  
   172  		if err != nil {
   173  			return nil, errors.New("Can't parse field 10 as unsigned integer in " + procDiskStatsFile)
   174  		}
   175  
   176  		ios.IOPending, err = strconv.ParseUint(strutil.ReadField(text, 11, true), 10, 64)
   177  
   178  		if err != nil {
   179  			return nil, errors.New("Can't parse field 11 as unsigned integer in " + procDiskStatsFile)
   180  		}
   181  
   182  		ios.IOMs, err = strconv.ParseUint(strutil.ReadField(text, 12, true), 10, 64)
   183  
   184  		if err != nil {
   185  			return nil, errors.New("Can't parse field 12 as unsigned integer in " + procDiskStatsFile)
   186  		}
   187  
   188  		ios.IOQueueMs, err = strconv.ParseUint(strutil.ReadField(text, 13, true), 10, 64)
   189  
   190  		if err != nil {
   191  			return nil, errors.New("Can't parse field 13 as unsigned integer in " + procDiskStatsFile)
   192  		}
   193  
   194  		iostats["/dev/"+device] = ios
   195  	}
   196  
   197  	return iostats, nil
   198  }
   199  
   200  // parseFSInfo parses fs info data
   201  func parseFSInfo(s *bufio.Scanner, calculateStats bool) (map[string]*FSUsage, error) {
   202  	var err error
   203  
   204  	info := make(map[string]*FSUsage)
   205  
   206  	for s.Scan() {
   207  		text := s.Text()
   208  
   209  		if text == "" || text[:1] == "#" || text[:1] != "/" {
   210  			continue
   211  		}
   212  
   213  		device := strutil.ReadField(text, 0, true)
   214  		path := strutil.ReadField(text, 1, true)
   215  		fsInfo := &FSUsage{Type: strutil.ReadField(text, 2, true)}
   216  
   217  		if !calculateStats {
   218  			continue
   219  		}
   220  
   221  		stats := &syscall.Statfs_t{}
   222  
   223  		err = syscall.Statfs(path, stats)
   224  
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  
   229  		fsDevice, err := filepath.EvalSymlinks(device)
   230  
   231  		if err == nil {
   232  			fsInfo.Device = fsDevice
   233  		} else {
   234  			fsInfo.Device = device
   235  		}
   236  
   237  		fsInfo.Used = (stats.Blocks * uint64(stats.Bsize)) - (stats.Bfree * uint64(stats.Bsize))
   238  		fsInfo.Total = fsInfo.Used + (stats.Bavail * uint64(stats.Bsize))
   239  		fsInfo.Free = fsInfo.Total - fsInfo.Used
   240  
   241  		info[path] = fsInfo
   242  	}
   243  
   244  	if len(info) == 0 {
   245  		return nil, errors.New("Can't parse file " + mtabFile)
   246  	}
   247  
   248  	return info, nil
   249  }
   250  
   251  // enable:disable[LOC,ABC,CYCLO]
   252  
   253  // getHZ returns number of processor clock ticks per second
   254  func getHZ() float64 {
   255  	// CLK_TCK is a constant on Linux
   256  	// https://git.musl-libc.org/cgit/musl/tree/src/conf/sysconf.c#n30
   257  	return 100
   258  }