go language service monitoring -- the use of gopsutil

brief introduction

Gopsutil is a Python tool library * * [psutil]( https://link.zhihu.com/?target=https%3A//github.com/giampaolo/psutil )**The transplanted version of Golang can help us easily obtain various system and hardware information. Gopsutil shields the differences between various systems for us and has very strong portability. With gopsutil, we no longer need to use syscall to call corresponding system methods for different systems. Even better, there is no cgo code in the implementation of gopsutil, which makes cross compilation possible.

gopsutil divides different functions into different sub packages:

  • CPU: CPU related;
  • Disk: disk related;
  • Docker: docker related;
  • Host: host related;
  • mem: memory related;
  • net: network related;
  • Process: process related;
  • winservices: Windows service related.

Import package

import github.com/shirou/gopsutil

Collect CPU Information

We know that there are two types of CPU cores, one is physical core and the other is logical core. The number of physical cores is the actual number of CPUs on the motherboard. A physical CPU can have multiple cores. These cores are called logical cores. CPU related functions in gopsutil are in the CPU sub package, which provides interfaces for obtaining the number of physical and logical cores and CPU utilization:

  • Counts(logical bool): pass in false to return the number of physical cores; pass in true to return the number of logical cores;
  • Percent(interval time.Duration, percpu bool): indicates to obtain the CPU utilization within the interval. When percpu is false, obtain the total CPU utilization. When percpu is true, obtain the utilization of each CPU respectively, and return a value of [] float64 type.
physicalCnt, _ := cpu.Counts(false) // cpu physical cores
logicalCnt, _ := cpu.Counts(true)   // Number of cpu logical cores
fmt.Printf("physical count:%d logical count:%d\n", physicalCnt, logicalCnt)

// Obtain the total CPU utilization rate and the respective utilization rate of each CPU within 3s
totalPercent, _ := cpu.Percent(3*time.Second, false)	// Total CPU utilization
perPercents, _ := cpu.Percent(3*time.Second, true)	// Utilization of each CPU
fmt.Printf("total percent:%v per percents:%v", totalPercent, perPercents)

The above code output:

physical count:6 logical count:12
total percent:[1.078981441519206] per percents:[13.19796954314721 1.5706806282722512 23.036649214659686 2.094240837696335 13.541666666666666 1.5625 8.854166666666668 1.0471204188481675 4.6875 1.5625 4.712041884816754 1.0416666666666665]

detailed information

Call CPU Info() to get the details of CPU and return [] CPU InfoStat:

func main() {
  infos, _ := cpu.Info()
  for _, info := range infos {
    data, _ := json.MarshalIndent(info, "", " ")
    fmt.Print(string(data))
  }
}

To facilitate viewing, I use JSON to output the results:

{
 "cpu": 0,
 "vendorId": "GenuineIntel",
 "family": "205",
 "model": "",
 "stepping": 0,
 "physicalId": "BFEBFBFF000A0653",
 "coreId": "",
 "cores": 12,	// Number of cpu logical cores
 "modelName": "Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz",
 "mhz": 2904,
 "cacheSize": 0,
 "flags": [],
 "microcode": ""
}

It can be seen from the results that the CPU is Intel's i5-10400 series, with a frequency of 2.90GHz. The above is the return result of my running on Windows. GitHub is used internally COM / stackexchange / WMI library. Under Linux, each logical CPU will return an InfoStat structure.

CPU load

info, _ := load.Avg()
fmt.Printf("%v\n", info)
{
    "load1":0,
    "load5":0,
    "load15":0
}

Time occupation

Call CPU Times (percpu bool) can obtain the total CPU and the time occupation of each individual CPU from startup. Passing in percpu=false returns the total, and passing in percpu=true returns the single. The time usage of each CPU is a TimeStat structure:

// src/github.com/shirou/gopsutil/cpu/cpu.go
type TimesStat struct {
  CPU       string  `json:"cpu"`	//CPU identification. If it is total, this field is' CPU total ', otherwise it is' cpu0', 'cpu1';
  User      float64 `json:"user"`	//User time occupation (user status)
  System    float64 `json:"system"`	//System time occupation (kernel state)
  Idle      float64 `json:"idle"`	//free time
  Nice      float64 `json:"nice"`
  Iowait    float64 `json:"iowait"`
  Irq       float64 `json:"irq"`
  Softirq   float64 `json:"softirq"`
  Steal     float64 `json:"steal"`
  Guest     float64 `json:"guest"`
  GuestNice float64 `json:"guestNice"`
}

For example:

func main() {
  infos, _ := cpu.Times(true)
  for _, info := range infos {
    data, _ := json.MarshalIndent(info, "", " ")
    fmt.Print(string(data))
  }
}

To facilitate viewing, I use JSON to output the results. Here is one of the outputs:

{
 "cpu": "cpu0",
 "user": 674.46875,
 "system": 1184.984375,
 "idle": 7497.1875,
 "nice": 0,
 "iowait": 0,
 "irq": 75.578125,
 "softirq": 0,
 "steal": 0,
 "guest": 0,
 "guestNice": 0
}

Collect disk information

The sub package disk is used to obtain disk information. Disk can obtain IO statistics, partition and utilization information. The following are introduced in turn.

IO statistics

Call disk Iocounters() function, the returned IO statistics are represented by map[string]IOCountersStat type. Each partition has a structure, the key is the partition name, and the value is statistical information. Some fields of the statistical structure are extracted here, mainly including the number of reads and writes, bytes and time:

// src/github.com/shirou/gopsutil/disk/disk.go
type IOCountersStat struct {
  ReadCount        uint64 `json:"readCount"`
  MergedReadCount  uint64 `json:"mergedReadCount"`
  WriteCount       uint64 `json:"writeCount"`
  MergedWriteCount uint64 `json:"mergedWriteCount"`
  ReadBytes        uint64 `json:"readBytes"`
  WriteBytes       uint64 `json:"writeBytes"`
  ReadTime         uint64 `json:"readTime"`
  WriteTime        uint64 `json:"writeTime"`
  // ...
}

For example:

func main() {
  mapStat, _ := disk.IOCounters()	//  IO statistics
  for name, stat := range mapStat {
    fmt.Println(name)
    data, _ := json.MarshalIndent(stat, "", "  ")
    fmt.Println(string(data))
  }
}

The output includes all partitions. I only show one here:

C:	// c disk
{
  "readCount": 2249456,      
  "mergedReadCount": 0,      
  "writeCount": 6522173,     
  "mergedWriteCount": 0,     
  "readBytes": 59965126656,  
  "writeBytes": 142391553536,
  "readTime": 5406,
  "writeTime": 3562,
  "iopsInProgress": 0,       
  "ioTime": 0,
  "weightedIO": 0,
  "name": "C:",
  "serialNumber": "",
  "label": ""
}

Note that disk Iocounters() can pass in a variable number of string parameters to identify the partition, which is not valid on Windows.

partition

Call disk The PartitionStat (all bool) function returns partition information. If all = false, only the actual physical partition (including hard disk, CD-ROM and USB) is returned, and other virtual partitions are ignored. If all = true, all partitions are returned. The return type is [] PartitionStat, and each partition corresponds to a PartitionStat structure:

// src/github.com/shirou/gopsutil/disk/
type PartitionStat struct {
  Device     string `json:"device"`		// Partition ID is in the format of 'C:' on Windows
  Mountpoint string `json:"mountpoint"`	// Mount point is the starting location of the file path of the partition
  Fstype     string `json:"fstype"`		// File system types: FAT, NTFS, etc. are commonly used in Windows, and ext, ext2, ext3, etc. are used in Linux
  Opts       string `json:"opts"`		// Options, system related
}

For example:

func main() {
  infos, _ := disk.Partitions(false)
  for _, info := range infos {
    data, _ := json.MarshalIndent(info, "", "  ")
    fmt.Println(string(data))
  }
}

My Windows machine output (only show the first partition):

{
  "device": "C:",      
  "mountpoint": "C:",  
  "fstype": "NTFS",    
  "opts": "rw.compress"
}

According to the above output, my first partition is C:, and the file system type is NTFS.

Utilization rate

Call disk Usage (path string) to obtain the usage of the disk where the path path is located, and return a UsageStat structure:

// src/github.com/shirou/gopsutil/disk.go
type UsageStat struct {
  Path              string  `json:"path"`	// Path, passed in parameters
  Fstype            string  `json:"fstype"`	// file system type
  Total             uint64  `json:"total"`	// Total capacity of this zone
  Free              uint64  `json:"free"`	// Free capacity
  Used              uint64  `json:"used"`	// Used capacity
  UsedPercent       float64 `json:"usedPercent"`	// Percentage used
  InodesTotal       uint64  `json:"inodesTotal"`
  InodesUsed        uint64  `json:"inodesUsed"`
  InodesFree        uint64  `json:"inodesFree"`
  InodesUsedPercent float64 `json:"inodesUsedPercent"`
}

For example:

func main() {
  info, _ := disk.Usage("E:/code")
  data, _ := json.MarshalIndent(info, "", "  ")
  fmt.Println(string(data))
}

Since the usage of the disk is returned, the paths E:/code and E: return the same result, but the Path field in the structure is different. Program output:

{
  "path": "E:/code",
  "fstype": "",
  "total": 339303460864,
  "free": 336575107072,
  "used": 2728353792,
  "usedPercent": 0.804104321557033,
  "inodesTotal": 0,
  "inodesUsed": 0,
  "inodesFree": 0,
  "inodesUsedPercent": 0
}

Collect host information

The sub package host can obtain host related information, such as boot time, kernel version number, platform information and so on.

Startup time

host.BootTime() returns the timestamp of the host boot time:

func main() {
  timestamp, _ := host.BootTime()
  t := time.Unix(int64(timestamp), 0)
  fmt.Println(t.Local().Format("2006-01-02 15:04:05"))
}

Get the boot time first, and then go through the above. Time UNIX () converts it to time Time type, the last output time in 2006-01-02 15:04:05 format:

2020-04-06 20:25:32

Kernel version and platform information

func main() {
  version, _ := host.KernelVersion()
  fmt.Println(version)

  platform, family, version, _ := host.PlatformInformation()
  fmt.Println("platform:", platform)	// Operating system information
  fmt.Println("family:", family)
  fmt.Println("version:", version)
}

Run output on my Win10:

10.0.19042 Build 19042
platform: Microsoft Windows 10 Pro
family: Standalone Workstation    
version: 10.0.19042 Build 19042

end user

host.Users() returns the user information connected to the terminal. Each user has a UserStat structure:

// src/github.com/shirou/gopsutil/host/host.go
type UserStat struct {
  User     string `json:"user"`
  Terminal string `json:"terminal"`
  Host     string `json:"host"`
  Started  int    `json:"started"`
}

The fields are clear at a glance. See the example:

func main() {
  users, _ := host.Users()
  for _, user := range users {
    data, _ := json.MarshalIndent(user, "", " ")
    fmt.Println(string(data))
  }
}

Collect memory information

Get physical memory information

We demonstrated how to use mem Virtualmemory() to get memory information. This function returns only physical memory information.

import "github.com/shirou/gopsutil/mem"
//mem. The memory structure information returned by the virtual memory () method VirtualMemoryStat
v, _ := mem.VirtualMemory()
fmt.Println(v)

The output is as follows:

{
  "total": 17013055488,		// Total memory
  "available": 8491429888,	// Available memory
  "used": 8521625600,		// Used memory
  "usedPercent": 50,		// Memory usage percentage
  "free": 8491429888,		// Unused memory
  "active": 0,
  "inactive": 0,
  "wired": 0,
  "laundry": 0,
  "buffers": 0,
  "cached": 0,
  "writeBack": 0,
  "dirty": 0,
  "writeBackTmp": 0,
  "shared": 0,
  "slab": 0,
  "sreclaimable": 0,
  "sunreclaim": 0,
  "pageTables": 0,
  "swapCached": 0,
  "commitLimit": 0,
  "committedAS": 0,
  "highTotal": 0,
  "highFree": 0,
  "lowTotal": 0,
  "lowFree": 0,
  "swapTotal": 0,
  "swapFree": 0,
  "mapped": 0,
  "vmallocTotal": 0,
  "vmallocUsed": 0,
  "vmallocChunk": 0,
  "hugePagesTotal": 0,
  "hugePagesFree": 0,
  "hugePageSize": 0
}

Get swap memory information

We can also use mem Swapmemory() gets the information of swap memory, which is stored in the structure SwapMemoryStat:

// src/github.com/shirou/gopsutil/mem/
type SwapMemoryStat struct {
  Total       uint64  `json:"total"`	// Total memory
  Used        uint64  `json:"used"`		// Memory used
  Free        uint64  `json:"free"`		// idle memory
  UsedPercent float64 `json:"usedPercent"`	// Memory usage
  Sin         uint64  `json:"sin"`
  Sout        uint64  `json:"sout"`
  PgIn        uint64  `json:"pgin"`		// Number of pages loaded
  PgOut       uint64  `json:"pgout"`	// Pages eliminated
  PgFault     uint64  `json:"pgfault"`	// Number of page missing errors
}

The meaning of the field is easy to understand. We will focus on the three fields PgIn/PgOut/PgFault. Swap memory is in pages. If a page fault occurs, the operating system will load some pages from the disk into memory and eliminate some pages in memory according to a specific mechanism. PgIn indicates the number of loaded pages, PgOut eliminated pages, and PgFault page missing errors.

For example:

func main() {
  swapMemory, _ := mem.SwapMemory()
  data, _ := json.MarshalIndent(swapMemory, "", " ")
  fmt.Println(string(data))
}

The output is as follows:

{
 "total": 24093069312,
 "used": 12682260480,
 "free": 11410808832,
 "usedPercent": 52.638625306587095,
 "sin": 0,
 "sout": 0,
 "pgin": 0,
 "pgout": 0,
 "pgfault": 0,
 "pgmajfault": 0
}

Collect process information

Process can be used to obtain the process information currently running in the system, create a new process, and perform some operations on the process.

func main() {
  var rootProcess *process.Process
  processes, _ := process.Processes()
  for _, p := range processes {
    if p.Pid == 0 {
      rootProcess = p
      break
    }
  }

  fmt.Println(rootProcess)

  fmt.Println("children:")
  children, _ := rootProcess.Children()
  for _, p := range children {
    fmt.Println(p)
  }
}

Call process first Processes () gets all the processes running in the current system, then finds the Pid 0 process, the first process of the operating system, and finally calls Children() to return to its sub process. There are also many ways to obtain process information. If you are interested, you can check the documentation~

Collect Windows service information

The winservices sub package can obtain the Service information in the Windows system, and golang.com is used internally Org / X / sys package. In winservices, a Service corresponds to a Service structure:

// src/github.com/shirou/gopsutil/winservices/winservices.go
type Service struct {
  Name   string
  Config mgr.Config
  Status ServiceStatus
  // contains filtered or unexported fields
}

mgr.Config is the package golang Org / X / sys, which records the service type, startup type (automatic / manual), binary file path and other information in detail:

// src/golang.org/x/sys/windows/svc/mgr/config.go
type Config struct {
  ServiceType      uint32
  StartType        uint32
  ErrorControl     uint32
  BinaryPathName   string
  LoadOrderGroup   string
  TagId            uint32
  Dependencies     []string
  ServiceStartName string
  DisplayName      string
  Password         string
  Description      string
  SidType          uint32
  DelayedAutoStart bool
}

The ServiceStatus structure records the status of the service:

// src/github.com/shirou/gopsutil/winservices/winservices.go
type ServiceStatus struct {
  State         svc.State	//It is in service status, including stopped, running, suspended, etc
  Accepts       svc.Accepted	// Indicates which operations the service receives, including pause, resume and session switching
  Pid           uint32	// Process ID
  Win32ExitCode uint32	// Application exit status code
}

In the following program, I output the names, binary file paths and status of all services in the system to the console:

func main() {
  services, _ := winservices.ListServices()

  for _, service := range services {
    newservice, _ := winservices.NewService(service.Name)
    newservice.GetServiceDetail()
    fmt.Println("Name:", newservice.Name, "Binary Path:", newservice.Config.BinaryPathName, "State: ", newservice.Status.State)
  }
}

Notice that you call WinServices The Service object information returned by ListServices () is incomplete. We create a service with the name of the service through NewService(), and then call the GetServiceDetail () method to get the details of the service. You cannot directly use service Call GetServiceDetail() because the object returned by ListService() lacks the necessary system resource handle (to save resources), calling GetServiceDetail() method will panic!!!

Error and timeout

Since most functions involve bottom-level system calls, errors and timeouts are inevitable. Almost all interfaces have two return values, the second as an error. In the previous example, we ignored the error in order to simplify the code. In practical use, it is recommended to deal with the error.

In addition, most interfaces are a pair, one without context A parameter of type context. The other parameter with this type is used for context control. In case of internal call error or timeout, it can be handled in time to avoid waiting for a long time to return. In fact, without context The function of context parameter is internally based on context Background() is a parameter call with context Function of context:

// src/github.com/shirou/gopsutil/cpu_windows.go
func Times(percpu bool) ([]TimesStat, error) {
  return TimesWithContext(context.Background(), percpu)
}

func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
  // ...
}

Added by envexlabs on Tue, 08 Mar 2022 06:38:12 +0200