gopkg.in/essentialkaos/ek.v3@v3.5.1/system/info.go (about)

     1  // +build !windows
     2  
     3  // Package system provides methods for working with system data (metrics/users)
     4  package system
     5  
     6  // ////////////////////////////////////////////////////////////////////////////////// //
     7  //                                                                                    //
     8  //                     Copyright (c) 2009-2016 Essential Kaos                         //
     9  //      Essential Kaos Open Source License <http://essentialkaos.com/ekol?en>         //
    10  //                                                                                    //
    11  // ////////////////////////////////////////////////////////////////////////////////// //
    12  
    13  import (
    14  	"errors"
    15  	"io/ioutil"
    16  	"path/filepath"
    17  	"strconv"
    18  	"strings"
    19  	"syscall"
    20  	"time"
    21  )
    22  
    23  // ////////////////////////////////////////////////////////////////////////////////// //
    24  
    25  const _Hz = 100.0
    26  
    27  const (
    28  	_PROC_UPTIME    = "/proc/uptime"
    29  	_PROC_LOADAVG   = "/proc/loadavg"
    30  	_PROC_MEMINFO   = "/proc/meminfo"
    31  	_PROC_CPUINFO   = "/proc/stat"
    32  	_PROC_NET       = "/proc/net/dev"
    33  	_PROC_DISCSTATS = "/proc/diskstats"
    34  	_MTAB_FILE      = "/etc/mtab"
    35  )
    36  
    37  // ////////////////////////////////////////////////////////////////////////////////// //
    38  
    39  // LoadAvg contains information about average system load
    40  type LoadAvg struct {
    41  	Min1  float64 `json:"min1"`  // LA in last 1 minute
    42  	Min5  float64 `json:"min5"`  // LA in last 5 minutes
    43  	Min15 float64 `json:"min15"` // LA in last 15 minutes
    44  	RProc int     `json:"rproc"` // Number of currently runnable kernel scheduling entities
    45  	TProc int     `json:"tproc"` // Number of kernel scheduling entities that currently exist on the system
    46  }
    47  
    48  // MemInfo contains info about system memory
    49  type MemInfo struct {
    50  	MemTotal   uint64 `json:"total"`       // Total usable ram (i.e. physical ram minus a few reserved bits and the kernel binary code)
    51  	MemFree    uint64 `json:"free"`        // The sum of MemFree - (Buffers + Cached)
    52  	MemUsed    uint64 `json:"used"`        // MemTotal - MemFree
    53  	Buffers    uint64 `json:"buffers"`     // Relatively temporary storage for raw disk blocks shouldn't get tremendously large (20MB or so)
    54  	Cached     uint64 `json:"cached"`      // In-memory cache for files read from the disk (the pagecache).  Doesn't include SwapCached
    55  	Active     uint64 `json:"active"`      // Memory that has been used more recently and usually not reclaimed unless absolutely necessary
    56  	Inactive   uint64 `json:"inactive"`    // Memory which has been less recently used.  It is more eligible to be reclaimed for other purposes
    57  	SwapTotal  uint64 `json:"swap_total"`  // Total amount of swap space available
    58  	SwapFree   uint64 `json:"swap_free"`   // Memory which has been evicted from RAM, and is temporarily on the disk still also is in the swapfile
    59  	SwapUsed   uint64 `json:"swap_used"`   // SwapTotal - SwapFree
    60  	SwapCached uint64 `json:"spaw_cached"` // Memory that once was swapped out, is swapped back in but
    61  	Dirty      uint64 `json:"dirty"`       // Memory which is waiting to get written back to the disk
    62  	Slab       uint64 `json:"slab"`        // In-kernel data structures cache
    63  }
    64  
    65  // CPUInfo contains info about CPU usage
    66  type CPUInfo struct {
    67  	User   float64 `json:"user"`   // Normal processes executing in user mode
    68  	System float64 `json:"system"` // Processes executing in kernel mode
    69  	Nice   float64 `json:"nice"`   // Niced processes executing in user mode
    70  	Idle   float64 `json:"idle"`   // Twiddling thumbs
    71  	Wait   float64 `json:"wait"`   // Waiting for I/O to complete
    72  	Count  int     `json:"count"`  // Number of CPU cores
    73  }
    74  
    75  // FSInfo contains info about fs usage
    76  type FSInfo struct {
    77  	Type    string   `json:"type"`    // FS type (ext4/ntfs/etc...)
    78  	Device  string   `json:"device"`  // Device spec
    79  	Used    uint64   `json:"used"`    // Used space
    80  	Free    uint64   `json:"free"`    // Free space
    81  	Total   uint64   `json:"total"`   // Total space
    82  	IOStats *IOStats `json:"iostats"` // IO statistics
    83  }
    84  
    85  // IOStats contains inforamtion about I/O
    86  type IOStats struct {
    87  	ReadComplete  uint64 `json:"read_complete"`  // Reads completed successfully
    88  	ReadMerged    uint64 `json:"read_merged"`    // Reads merged
    89  	ReadSectors   uint64 `json:"read_sectors"`   // Sectors read
    90  	ReadMs        uint64 `json:"read_ms"`        // Time spent reading (ms)
    91  	WriteComplete uint64 `json:"write_complete"` // Writes completed
    92  	WriteMerged   uint64 `json:"write_merged"`   // Writes merged
    93  	WriteSectors  uint64 `json:"write_sectors"`  // Sectors written
    94  	WriteMs       uint64 `json:"write_ms"`       // Time spent writing (ms)
    95  	IOPending     uint64 `json:"io_pending"`     // I/Os currently in progress
    96  	IOMs          uint64 `json:"io_ms"`          // Time spent doing I/Os (ms)
    97  	IOQueueMs     uint64 `json:"io_queue_ms"`    // Weighted time spent doing I/Os (ms)
    98  }
    99  
   100  // SystemInfo contains info about system (hostname, OS, arch...)
   101  type SystemInfo struct {
   102  	Hostname string `json:"hostname"` // Hostname
   103  	OS       string `json:"os"`       // OS name
   104  	Kernel   string `json:"kernel"`   // Kernel version
   105  	Arch     string `json:"arch"`     // System architecture (i386/i686/x86_64/etc...)
   106  }
   107  
   108  // InterfaceInfo contains info about network interfaces
   109  type InterfaceInfo struct {
   110  	ReceivedBytes      uint64 `json:"received_bytes"`
   111  	ReceivedPackets    uint64 `json:"received_packets"`
   112  	TransmittedBytes   uint64 `json:"transmitted_bytes"`
   113  	TransmittedPackets uint64 `json:"transmitted_packets"`
   114  }
   115  
   116  // ////////////////////////////////////////////////////////////////////////////////// //
   117  
   118  // GetUptime return uptime in seconds from 1/1/1970
   119  func GetUptime() (uint64, error) {
   120  	content, err := readFileContent(_PROC_UPTIME)
   121  
   122  	if err != nil {
   123  		return 0, err
   124  	}
   125  
   126  	ca := strings.Split(content[0], " ")
   127  
   128  	if len(ca) != 2 {
   129  		return 0, errors.New("Can't parse file " + _PROC_UPTIME + ".")
   130  	}
   131  
   132  	up, _ := strconv.ParseFloat(ca[0], 64)
   133  
   134  	return uint64(up), nil
   135  }
   136  
   137  // GetLA return loadavg
   138  func GetLA() (*LoadAvg, error) {
   139  	result := &LoadAvg{}
   140  	content, err := readFileContent(_PROC_LOADAVG)
   141  
   142  	if err != nil {
   143  		return result, err
   144  	}
   145  
   146  	contentSlice := strings.Split(content[0], " ")
   147  
   148  	if len(contentSlice) != 5 {
   149  		return result, errors.New("Can't parse file " + _PROC_LOADAVG + ".")
   150  	}
   151  
   152  	procSlice := strings.Split(contentSlice[3], "/")
   153  
   154  	result.Min1, _ = strconv.ParseFloat(contentSlice[0], 64)
   155  	result.Min5, _ = strconv.ParseFloat(contentSlice[1], 64)
   156  	result.Min15, _ = strconv.ParseFloat(contentSlice[2], 64)
   157  	result.RProc, _ = strconv.Atoi(procSlice[0])
   158  	result.TProc, _ = strconv.Atoi(procSlice[1])
   159  
   160  	return result, nil
   161  }
   162  
   163  // GetMemInfo return memory info
   164  func GetMemInfo() (*MemInfo, error) {
   165  	var props = map[string]bool{
   166  		"MemTotal":   true,
   167  		"MemFree":    true,
   168  		"Buffers":    true,
   169  		"Cached":     true,
   170  		"SwapCached": true,
   171  		"Active":     true,
   172  		"Inactive":   true,
   173  		"SwapTotal":  true,
   174  		"SwapFree":   true,
   175  		"Dirty":      true,
   176  		"Slab":       true,
   177  	}
   178  
   179  	result := &MemInfo{}
   180  	content, err := readFileContent(_PROC_MEMINFO)
   181  
   182  	if err != nil {
   183  		return result, err
   184  	}
   185  
   186  	for _, line := range content {
   187  		if line == "" {
   188  			continue
   189  		}
   190  
   191  		lineSlice := strings.Split(line, ":")
   192  
   193  		if len(lineSlice) != 2 {
   194  			return result, errors.New("Can't parse file " + _PROC_MEMINFO + ".")
   195  		}
   196  
   197  		if props[lineSlice[0]] != true {
   198  			continue
   199  		}
   200  
   201  		strValue := strings.TrimRight(lineSlice[1], " kB")
   202  		strValue = strings.Replace(strValue, " ", "", -1)
   203  		uintValue, err := strconv.ParseUint(strValue, 10, 64)
   204  
   205  		if err != nil {
   206  			return result, err
   207  		}
   208  
   209  		switch lineSlice[0] {
   210  		case "MemTotal":
   211  			result.MemTotal = uintValue * 1024
   212  		case "MemFree":
   213  			result.MemFree = uintValue * 1024
   214  		case "Buffers":
   215  			result.Buffers = uintValue * 1024
   216  		case "Cached":
   217  			result.Cached = uintValue * 1024
   218  		case "SwapCached":
   219  			result.SwapCached = uintValue * 1024
   220  		case "Active":
   221  			result.Active = uintValue * 1024
   222  		case "Inactive":
   223  			result.Inactive = uintValue * 1024
   224  		case "SwapTotal":
   225  			result.SwapTotal = uintValue * 1024
   226  		case "SwapFree":
   227  			result.SwapFree = uintValue * 1024
   228  		case "Dirty":
   229  			result.Dirty = uintValue * 1024
   230  		case "Slab":
   231  			result.Slab = uintValue * 1024
   232  		}
   233  	}
   234  
   235  	result.MemFree += result.Cached + result.Buffers
   236  	result.MemUsed = result.MemTotal - result.MemFree
   237  	result.SwapUsed = result.SwapTotal - result.SwapFree
   238  
   239  	return result, nil
   240  }
   241  
   242  // GetCPUInfo return info about CPU usage
   243  func GetCPUInfo() (*CPUInfo, error) {
   244  	result := &CPUInfo{}
   245  
   246  	user, system, nice, idle, wait, total, count, err := getCPUStats()
   247  
   248  	if err != nil {
   249  		return result, err
   250  	}
   251  
   252  	result.System = (float64(system) / float64(total)) * 100
   253  	result.User = (float64(user) / float64(total)) * 100
   254  	result.Nice = (float64(nice) / float64(total)) * 100
   255  	result.Wait = (float64(wait) / float64(total)) * 100
   256  	result.Idle = (float64(idle) / float64(total)) * 100
   257  	result.Count = count
   258  
   259  	return result, nil
   260  }
   261  
   262  // GetFSInfo return info about mounted filesystems
   263  func GetFSInfo() (map[string]*FSInfo, error) {
   264  	result := make(map[string]*FSInfo)
   265  
   266  	content, err := readFileContent(_MTAB_FILE)
   267  
   268  	if err != nil {
   269  		return result, err
   270  	}
   271  
   272  	ios, err := GetIOStats()
   273  
   274  	if err != nil {
   275  		return result, err
   276  	}
   277  
   278  	for _, line := range content {
   279  		if line == "" || line[0:1] == "#" || line[0:1] != "/" {
   280  			continue
   281  		}
   282  
   283  		values := strings.Split(line, " ")
   284  
   285  		if len(values) < 4 {
   286  			return result, errors.New("Can't parse file " + _MTAB_FILE)
   287  		}
   288  
   289  		path := values[1]
   290  		fsInfo := &FSInfo{Type: values[2]}
   291  		stats := &syscall.Statfs_t{}
   292  
   293  		err = syscall.Statfs(path, stats)
   294  
   295  		if err != nil {
   296  			return result, err
   297  		}
   298  
   299  		fsDevice, err := filepath.EvalSymlinks(values[0])
   300  
   301  		if err == nil {
   302  			fsInfo.Device = fsDevice
   303  		} else {
   304  			fsInfo.Device = values[0]
   305  		}
   306  
   307  		fsInfo.Total = stats.Blocks * uint64(stats.Bsize)
   308  		fsInfo.Free = uint64(stats.Bavail) * uint64(stats.Bsize)
   309  		fsInfo.Used = fsInfo.Total - (stats.Bfree * uint64(stats.Bsize))
   310  		fsInfo.IOStats = ios[strings.Replace(fsInfo.Device, "/dev/", "", 1)]
   311  
   312  		result[path] = fsInfo
   313  	}
   314  
   315  	return result, nil
   316  }
   317  
   318  // GetIOStats return I/O stats
   319  func GetIOStats() (map[string]*IOStats, error) {
   320  	result := make(map[string]*IOStats)
   321  
   322  	content, err := readFileContent(_PROC_DISCSTATS)
   323  
   324  	if err != nil {
   325  		return result, err
   326  	}
   327  
   328  	for _, line := range content {
   329  		if line == "" {
   330  			continue
   331  		}
   332  
   333  		values := cleanSlice(strings.Split(line, " "))
   334  
   335  		if len(values) != 14 {
   336  			return result, errors.New("Can't parse file " + _PROC_DISCSTATS)
   337  		}
   338  
   339  		device := values[2]
   340  
   341  		if device[0:3] == "ram" || device[0:3] == "loo" {
   342  			continue
   343  		}
   344  
   345  		metrics := stringSliceToUintSlice(values[3:])
   346  
   347  		result[device] = &IOStats{
   348  			ReadComplete:  metrics[0],
   349  			ReadMerged:    metrics[1],
   350  			ReadSectors:   metrics[2],
   351  			ReadMs:        metrics[3],
   352  			WriteComplete: metrics[4],
   353  			WriteMerged:   metrics[5],
   354  			WriteSectors:  metrics[6],
   355  			WriteMs:       metrics[7],
   356  			IOPending:     metrics[8],
   357  			IOMs:          metrics[9],
   358  			IOQueueMs:     metrics[10],
   359  		}
   360  	}
   361  
   362  	return result, nil
   363  }
   364  
   365  // GetInterfacesInfo return info about network interfaces
   366  func GetInterfacesInfo() (map[string]*InterfaceInfo, error) {
   367  	result := make(map[string]*InterfaceInfo)
   368  
   369  	content, err := readFileContent(_PROC_NET)
   370  
   371  	if err != nil {
   372  		return result, err
   373  	}
   374  
   375  	if len(content) <= 2 {
   376  		return result, nil
   377  	}
   378  
   379  	for _, line := range content[2:] {
   380  		lineSlice := strings.Split(line, ":")
   381  
   382  		if len(lineSlice) != 2 {
   383  			continue
   384  		}
   385  
   386  		metrics := cleanSlice(strings.Split(lineSlice[1], " "))
   387  		name := strings.TrimLeft(lineSlice[0], " ")
   388  		receivedBytes, _ := strconv.ParseUint(metrics[0], 10, 64)
   389  		receivedPackets, _ := strconv.ParseUint(metrics[1], 10, 64)
   390  		transmittedBytes, _ := strconv.ParseUint(metrics[8], 10, 64)
   391  		transmittedPackets, _ := strconv.ParseUint(metrics[9], 10, 64)
   392  
   393  		result[name] = &InterfaceInfo{
   394  			receivedBytes,
   395  			receivedPackets,
   396  			transmittedBytes,
   397  			transmittedPackets,
   398  		}
   399  	}
   400  
   401  	return result, nil
   402  }
   403  
   404  // GetNetworkSpeed return input/output speed in bytes per second
   405  func GetNetworkSpeed() (uint64, uint64, error) {
   406  	intInfo1, err := GetInterfacesInfo()
   407  
   408  	if err != nil {
   409  		return 0, 0, err
   410  	}
   411  
   412  	time.Sleep(time.Second)
   413  
   414  	intInfo2, err := GetInterfacesInfo()
   415  
   416  	if err != nil {
   417  		return 0, 0, err
   418  	}
   419  
   420  	rb1, tb1 := getActiveInterfacesBytes(intInfo1)
   421  	rb2, tb2 := getActiveInterfacesBytes(intInfo2)
   422  
   423  	if rb1+tb1 == 0 || rb2+tb2 == 0 {
   424  		return 0, 0, nil
   425  	}
   426  
   427  	return rb2 - rb1, tb2 - tb1, nil
   428  }
   429  
   430  // GetIOUtil return IO utilization
   431  func GetIOUtil() (map[string]float64, error) {
   432  	result := make(map[string]float64)
   433  
   434  	fsInfoPrev, err := GetFSInfo()
   435  
   436  	if err != nil {
   437  		return result, err
   438  	}
   439  
   440  	userPrev, systemPrev, _, idlePrev, waitPrev, _, count, err := getCPUStats()
   441  
   442  	if err != nil {
   443  		return result, err
   444  	}
   445  
   446  	time.Sleep(time.Second)
   447  
   448  	fsInfoCur, err := GetFSInfo()
   449  
   450  	if err != nil {
   451  		return result, err
   452  	}
   453  
   454  	userCur, systemCur, _, idleCur, waitCur, _, _, err := getCPUStats()
   455  
   456  	if err != nil {
   457  		return result, err
   458  	}
   459  
   460  	deltams := 1000.0 * (float64(userCur+systemCur+idleCur+waitCur) - float64(userPrev+systemPrev+idlePrev+waitPrev)) / float64(count) / _Hz
   461  
   462  	for n, f := range fsInfoPrev {
   463  		if fsInfoPrev[n].IOStats != nil && fsInfoCur[n].IOStats != nil {
   464  			ticks := float64(fsInfoCur[n].IOStats.IOQueueMs - fsInfoPrev[n].IOStats.IOQueueMs)
   465  			util := 100.0 * ticks / deltams
   466  
   467  			if util > 100.0 {
   468  				util = 100.0
   469  			}
   470  
   471  			result[f.Device] = util
   472  		}
   473  	}
   474  
   475  	return result, nil
   476  }
   477  
   478  // ////////////////////////////////////////////////////////////////////////////////// //
   479  
   480  func readFileContent(file string) ([]string, error) {
   481  	result := []string{}
   482  
   483  	content, err := ioutil.ReadFile(file)
   484  
   485  	if err != nil {
   486  		return result, err
   487  	}
   488  
   489  	result = strings.Split(string(content), "\n")
   490  
   491  	if len(result) == 0 {
   492  		return result, errors.New("File " + file + " is empty")
   493  	}
   494  
   495  	return result, nil
   496  }
   497  
   498  func cleanSlice(s []string) []string {
   499  	var result []string
   500  
   501  	for _, item := range s {
   502  		if item != "" {
   503  			result = append(result, item)
   504  		}
   505  	}
   506  
   507  	return result
   508  }
   509  
   510  func byteSliceToString(s [65]int8) string {
   511  	result := ""
   512  
   513  	for _, r := range s {
   514  		if r == 0 {
   515  			break
   516  		}
   517  
   518  		result += string(r)
   519  	}
   520  
   521  	return result
   522  }
   523  
   524  func stringSliceToUintSlice(s []string) []uint64 {
   525  	var result []uint64
   526  
   527  	for _, i := range s {
   528  		iu, _ := strconv.ParseUint(i, 10, 64)
   529  		result = append(result, iu)
   530  	}
   531  
   532  	return result
   533  }
   534  
   535  func getActiveInterfacesBytes(is map[string]*InterfaceInfo) (uint64, uint64) {
   536  	var (
   537  		received    uint64
   538  		transmitted uint64
   539  	)
   540  
   541  	for name, info := range is {
   542  		if len(name) < 3 || name[0:3] != "eth" {
   543  			continue
   544  		}
   545  
   546  		if info.ReceivedBytes == 0 && info.TransmittedBytes == 0 {
   547  			continue
   548  		}
   549  
   550  		received += info.ReceivedBytes
   551  		transmitted += info.TransmittedBytes
   552  	}
   553  
   554  	return received, transmitted
   555  }
   556  
   557  func getCPUStats() (uint64, uint64, uint64, uint64, uint64, uint64, int, error) {
   558  	content, err := readFileContent(_PROC_CPUINFO)
   559  
   560  	if err != nil {
   561  		return 0, 0, 0, 0, 0, 0, 0, errors.New("Can't parse file " + _PROC_CPUINFO + ".")
   562  	}
   563  
   564  	if len(content) <= 1 {
   565  		return 0, 0, 0, 0, 0, 0, 0, err
   566  	}
   567  
   568  	var count int
   569  
   570  	for _, line := range content {
   571  		if line != "" && line[0:3] == "cpu" && line[0:4] != "cpu " {
   572  			count++
   573  		}
   574  	}
   575  
   576  	cpu := strings.Replace(content[0], "cpu  ", "", -1)
   577  	cpua := strings.Split(cpu, " ")
   578  
   579  	var user, system, nice, idle, wait, irq, srq, steal, total uint64
   580  
   581  	user, _ = strconv.ParseUint(cpua[0], 10, 64)
   582  	nice, _ = strconv.ParseUint(cpua[1], 10, 64)
   583  	system, _ = strconv.ParseUint(cpua[2], 10, 64)
   584  	idle, _ = strconv.ParseUint(cpua[3], 10, 64)
   585  	wait, _ = strconv.ParseUint(cpua[4], 10, 64)
   586  	irq, _ = strconv.ParseUint(cpua[5], 10, 64)
   587  	srq, _ = strconv.ParseUint(cpua[6], 10, 64)
   588  	steal, _ = strconv.ParseUint(cpua[7], 10, 64)
   589  
   590  	total = user + system + nice + idle + wait + irq + srq + steal
   591  
   592  	return user, system, nice, idle, wait, total, count, nil
   593  }