github.com/minio/madmin-go@v1.7.5/health.go (about)

     1  //
     2  // MinIO Object Storage (c) 2021 MinIO, Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //      http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package madmin
    18  
    19  import (
    20  	"bufio"
    21  	"context"
    22  	"encoding/json"
    23  	"errors"
    24  	"net/http"
    25  	"net/url"
    26  	"os"
    27  	"os/exec"
    28  	"runtime"
    29  	"strings"
    30  	"syscall"
    31  	"time"
    32  
    33  	"github.com/minio/madmin-go/cgroup"
    34  	"github.com/minio/madmin-go/kernel"
    35  	"github.com/prometheus/procfs"
    36  	"github.com/shirou/gopsutil/v3/cpu"
    37  	"github.com/shirou/gopsutil/v3/disk"
    38  	"github.com/shirou/gopsutil/v3/host"
    39  	"github.com/shirou/gopsutil/v3/mem"
    40  	"github.com/shirou/gopsutil/v3/process"
    41  )
    42  
    43  const (
    44  	// HealthInfoVersion0 is version 0
    45  	HealthInfoVersion0 = ""
    46  	// HealthInfoVersion1 is version 1
    47  	HealthInfoVersion1 = "1"
    48  	// HealthInfoVersion2 is version 2
    49  	HealthInfoVersion2 = "2"
    50  	// HealthInfoVersion3 is version 3
    51  	HealthInfoVersion3 = "3"
    52  	// HealthInfoVersion is current health info version.
    53  	HealthInfoVersion = HealthInfoVersion3
    54  )
    55  
    56  const (
    57  	SysErrAuditEnabled      = "audit is enabled"
    58  	SysErrUpdatedbInstalled = "updatedb is installed"
    59  )
    60  
    61  const (
    62  	SrvSELinux      = "selinux"
    63  	SrvNotInstalled = "not-installed"
    64  )
    65  
    66  // NodeInfo - Interface to abstract any struct that contains address/endpoint and error fields
    67  type NodeInfo interface {
    68  	GetAddr() string
    69  	SetAddr(addr string)
    70  	SetError(err string)
    71  }
    72  
    73  // NodeCommon - Common fields across most node-specific health structs
    74  type NodeCommon struct {
    75  	Addr  string `json:"addr"`
    76  	Error string `json:"error,omitempty"`
    77  }
    78  
    79  // GetAddr - return the address of the node
    80  func (n *NodeCommon) GetAddr() string {
    81  	return n.Addr
    82  }
    83  
    84  // SetAddr - set the address of the node
    85  func (n *NodeCommon) SetAddr(addr string) {
    86  	n.Addr = addr
    87  }
    88  
    89  // SetError - set the address of the node
    90  func (n *NodeCommon) SetError(err string) {
    91  	n.Error = err
    92  }
    93  
    94  // SysErrors - contains a system error
    95  type SysErrors struct {
    96  	NodeCommon
    97  
    98  	Errors []string `json:"errors,omitempty"`
    99  }
   100  
   101  // SysServices - info about services that affect minio
   102  type SysServices struct {
   103  	NodeCommon
   104  
   105  	Services []SysService `json:"services,omitempty"`
   106  }
   107  
   108  // SysConfig - info about services that affect minio
   109  type SysConfig struct {
   110  	NodeCommon
   111  
   112  	Config map[string]interface{} `json:"config,omitempty"`
   113  }
   114  
   115  // SysService - name and status of a sys service
   116  type SysService struct {
   117  	Name   string `json:"name"`
   118  	Status string `json:"status"`
   119  }
   120  
   121  // CPU contains system's CPU information.
   122  type CPU struct {
   123  	VendorID   string   `json:"vendor_id"`
   124  	Family     string   `json:"family"`
   125  	Model      string   `json:"model"`
   126  	Stepping   int32    `json:"stepping"`
   127  	PhysicalID string   `json:"physical_id"`
   128  	ModelName  string   `json:"model_name"`
   129  	Mhz        float64  `json:"mhz"`
   130  	CacheSize  int32    `json:"cache_size"`
   131  	Flags      []string `json:"flags"`
   132  	Microcode  string   `json:"microcode"`
   133  	Cores      int      `json:"cores"` // computed
   134  }
   135  
   136  // CPUs contains all CPU information of a node.
   137  type CPUs struct {
   138  	NodeCommon
   139  
   140  	CPUs          []CPU `json:"cpus,omitempty"`
   141  	IsFreqGovPerf *bool `json:"is_freq_gov_perf,omitempty"`
   142  }
   143  
   144  // GetCPUs returns system's all CPU information.
   145  func GetCPUs(ctx context.Context, addr string) CPUs {
   146  	infos, err := cpu.InfoWithContext(ctx)
   147  	if err != nil {
   148  		return CPUs{
   149  			NodeCommon: NodeCommon{
   150  				Addr:  addr,
   151  				Error: err.Error(),
   152  			},
   153  		}
   154  	}
   155  
   156  	cpuMap := map[string]CPU{}
   157  	for _, info := range infos {
   158  		cpu, found := cpuMap[info.PhysicalID]
   159  		if found {
   160  			cpu.Cores++
   161  		} else {
   162  			cpu = CPU{
   163  				VendorID:   info.VendorID,
   164  				Family:     info.Family,
   165  				Model:      info.Model,
   166  				Stepping:   info.Stepping,
   167  				PhysicalID: info.PhysicalID,
   168  				ModelName:  info.ModelName,
   169  				Mhz:        info.Mhz,
   170  				CacheSize:  info.CacheSize,
   171  				Flags:      info.Flags,
   172  				Microcode:  info.Microcode,
   173  				Cores:      1,
   174  			}
   175  		}
   176  		cpuMap[info.PhysicalID] = cpu
   177  	}
   178  
   179  	cpus := []CPU{}
   180  	for _, cpu := range cpuMap {
   181  		cpus = append(cpus, cpu)
   182  	}
   183  
   184  	var igp *bool
   185  	isGovPerf, err := isFreqGovPerf()
   186  	if err == nil {
   187  		igp = &isGovPerf
   188  	}
   189  
   190  	return CPUs{
   191  		NodeCommon:    NodeCommon{Addr: addr},
   192  		CPUs:          cpus,
   193  		IsFreqGovPerf: igp,
   194  	}
   195  }
   196  
   197  // Partition contains disk partition's information.
   198  type Partition struct {
   199  	Error string `json:"error,omitempty"`
   200  
   201  	Device       string `json:"device,omitempty"`
   202  	Mountpoint   string `json:"mountpoint,omitempty"`
   203  	FSType       string `json:"fs_type,omitempty"`
   204  	MountOptions string `json:"mount_options,omitempty"`
   205  	MountFSType  string `json:"mount_fs_type,omitempty"`
   206  	SpaceTotal   uint64 `json:"space_total,omitempty"`
   207  	SpaceFree    uint64 `json:"space_free,omitempty"`
   208  	InodeTotal   uint64 `json:"inode_total,omitempty"`
   209  	InodeFree    uint64 `json:"inode_free,omitempty"`
   210  }
   211  
   212  // Partitions contains all disk partitions information of a node.
   213  type Partitions struct {
   214  	NodeCommon
   215  
   216  	Partitions []Partition `json:"partitions,omitempty"`
   217  }
   218  
   219  // GetPartitions returns all disk partitions information of a node running linux only operating system.
   220  func GetPartitions(ctx context.Context, addr string) Partitions {
   221  	if runtime.GOOS != "linux" {
   222  		return Partitions{
   223  			NodeCommon: NodeCommon{
   224  				Addr:  addr,
   225  				Error: "unsupported operating system " + runtime.GOOS,
   226  			},
   227  		}
   228  	}
   229  
   230  	parts, err := disk.PartitionsWithContext(ctx, false)
   231  	if err != nil {
   232  		return Partitions{
   233  			NodeCommon: NodeCommon{
   234  				Addr:  addr,
   235  				Error: err.Error(),
   236  			},
   237  		}
   238  	}
   239  
   240  	partitions := []Partition{}
   241  
   242  	for i := range parts {
   243  		usage, err := disk.UsageWithContext(ctx, parts[i].Mountpoint)
   244  		if err != nil {
   245  			partitions = append(partitions, Partition{
   246  				Device: parts[i].Device,
   247  				Error:  err.Error(),
   248  			})
   249  		} else {
   250  			partitions = append(partitions, Partition{
   251  				Device:       parts[i].Device,
   252  				Mountpoint:   parts[i].Mountpoint,
   253  				FSType:       parts[i].Fstype,
   254  				MountOptions: strings.Join(parts[i].Opts, ","),
   255  				MountFSType:  usage.Fstype,
   256  				SpaceTotal:   usage.Total,
   257  				SpaceFree:    usage.Free,
   258  				InodeTotal:   usage.InodesTotal,
   259  				InodeFree:    usage.InodesFree,
   260  			})
   261  		}
   262  	}
   263  
   264  	return Partitions{
   265  		NodeCommon: NodeCommon{Addr: addr},
   266  		Partitions: partitions,
   267  	}
   268  }
   269  
   270  // OSInfo contains operating system's information.
   271  type OSInfo struct {
   272  	NodeCommon
   273  
   274  	Info    host.InfoStat          `json:"info,omitempty"`
   275  	Sensors []host.TemperatureStat `json:"sensors,omitempty"`
   276  }
   277  
   278  // TimeInfo contains current time with timezone, and
   279  // the roundtrip duration when fetching it remotely
   280  type TimeInfo struct {
   281  	CurrentTime       time.Time `json:"current_time"`
   282  	RoundtripDuration int32     `json:"roundtrip_duration"`
   283  	TimeZone          string    `json:"time_zone"`
   284  }
   285  
   286  // GetOSInfo returns linux only operating system's information.
   287  func GetOSInfo(ctx context.Context, addr string) OSInfo {
   288  	if runtime.GOOS != "linux" {
   289  		return OSInfo{
   290  			NodeCommon: NodeCommon{
   291  				Addr:  addr,
   292  				Error: "unsupported operating system " + runtime.GOOS,
   293  			},
   294  		}
   295  	}
   296  
   297  	kr, err := kernel.CurrentRelease()
   298  	if err != nil {
   299  		return OSInfo{
   300  			NodeCommon: NodeCommon{
   301  				Addr:  addr,
   302  				Error: err.Error(),
   303  			},
   304  		}
   305  	}
   306  
   307  	info, err := host.InfoWithContext(ctx)
   308  	if err != nil {
   309  		return OSInfo{
   310  			NodeCommon: NodeCommon{
   311  				Addr:  addr,
   312  				Error: err.Error(),
   313  			},
   314  		}
   315  	}
   316  
   317  	osInfo := OSInfo{
   318  		NodeCommon: NodeCommon{Addr: addr},
   319  		Info:       *info,
   320  	}
   321  	osInfo.Info.KernelVersion = kr
   322  
   323  	osInfo.Sensors, err = host.SensorsTemperaturesWithContext(ctx)
   324  	if err != nil {
   325  		if _, isWarningErr := err.(*host.Warnings); !isWarningErr {
   326  			osInfo.Error = err.Error()
   327  		}
   328  	}
   329  
   330  	return osInfo
   331  }
   332  
   333  // GetSysConfig returns config values from the system
   334  // (only those affecting minio performance)
   335  func GetSysConfig(ctx context.Context, addr string) SysConfig {
   336  	sc := SysConfig{
   337  		NodeCommon: NodeCommon{Addr: addr},
   338  		Config:     map[string]interface{}{},
   339  	}
   340  	proc, err := procfs.Self()
   341  	if err != nil {
   342  		sc.Error = "rlimit: " + err.Error()
   343  	} else {
   344  		limits, err := proc.Limits()
   345  		if err != nil {
   346  			sc.Error = "rlimit: " + err.Error()
   347  		}
   348  		sc.Config["rlimit-max"] = limits.OpenFiles
   349  	}
   350  
   351  	zone, _ := time.Now().Zone()
   352  	sc.Config["time-info"] = TimeInfo{
   353  		CurrentTime: time.Now(),
   354  		TimeZone:    zone,
   355  	}
   356  
   357  	return sc
   358  }
   359  
   360  // GetSysServices returns info of sys services that affect minio
   361  func GetSysServices(ctx context.Context, addr string) SysServices {
   362  	ss := SysServices{
   363  		NodeCommon: NodeCommon{Addr: addr},
   364  		Services:   []SysService{},
   365  	}
   366  	srv, e := getSELinuxInfo()
   367  	if e != nil {
   368  		ss.Error = e.Error()
   369  	} else {
   370  		ss.Services = append(ss.Services, srv)
   371  	}
   372  
   373  	return ss
   374  }
   375  
   376  func getSELinuxInfo() (SysService, error) {
   377  	ss := SysService{Name: SrvSELinux}
   378  
   379  	file, err := os.Open("/etc/selinux/config")
   380  	if err != nil {
   381  		if errors.Is(err, os.ErrNotExist) {
   382  			ss.Status = SrvNotInstalled
   383  			return ss, nil
   384  		}
   385  		return ss, err
   386  	}
   387  	defer file.Close()
   388  
   389  	scanner := bufio.NewScanner(file)
   390  	for scanner.Scan() {
   391  		tokens := strings.SplitN(strings.TrimSpace(scanner.Text()), "=", 2)
   392  		if len(tokens) == 2 && tokens[0] == "SELINUX" {
   393  			ss.Status = tokens[1]
   394  			return ss, nil
   395  		}
   396  	}
   397  
   398  	return ss, scanner.Err()
   399  }
   400  
   401  // GetSysErrors returns issues in system setup/config
   402  func GetSysErrors(ctx context.Context, addr string) SysErrors {
   403  	se := SysErrors{NodeCommon: NodeCommon{Addr: addr}}
   404  	if runtime.GOOS != "linux" {
   405  		return se
   406  	}
   407  
   408  	ae, err := isAuditEnabled()
   409  	if err != nil {
   410  		se.Error = "audit: " + err.Error()
   411  	} else if ae {
   412  		se.Errors = append(se.Errors, SysErrAuditEnabled)
   413  	}
   414  
   415  	_, err = exec.LookPath("updatedb")
   416  	if err == nil {
   417  		se.Errors = append(se.Errors, SysErrUpdatedbInstalled)
   418  	} else if !strings.HasSuffix(err.Error(), exec.ErrNotFound.Error()) {
   419  		errMsg := "updatedb: " + err.Error()
   420  		if len(se.Error) == 0 {
   421  			se.Error = errMsg
   422  		} else {
   423  			se.Error = se.Error + ", " + errMsg
   424  		}
   425  	}
   426  
   427  	return se
   428  }
   429  
   430  // Audit is enabled if either `audit=1` is present in /proc/cmdline
   431  // or the `kauditd` process is running
   432  func isAuditEnabled() (bool, error) {
   433  	file, err := os.Open("/proc/cmdline")
   434  	if err != nil {
   435  		return false, err
   436  	}
   437  	defer file.Close()
   438  
   439  	scanner := bufio.NewScanner(file)
   440  	for scanner.Scan() {
   441  		if strings.Contains(scanner.Text(), "audit=1") {
   442  			return true, nil
   443  		}
   444  	}
   445  
   446  	return isKauditdRunning()
   447  }
   448  
   449  func isKauditdRunning() (bool, error) {
   450  	procs, err := process.Processes()
   451  	if err != nil {
   452  		return false, err
   453  	}
   454  	for _, proc := range procs {
   455  		pname, err := proc.Name()
   456  		if err != nil && pname == "kauditd" {
   457  			return true, nil
   458  		}
   459  	}
   460  	return false, nil
   461  }
   462  
   463  // MemInfo contains system's RAM and swap information.
   464  type MemInfo struct {
   465  	NodeCommon
   466  
   467  	Total          uint64 `json:"total,omitempty"`
   468  	Available      uint64 `json:"available,omitempty"`
   469  	SwapSpaceTotal uint64 `json:"swap_space_total,omitempty"`
   470  	SwapSpaceFree  uint64 `json:"swap_space_free,omitempty"`
   471  	// Limit will store cgroup limit if configured and
   472  	// less than Total, otherwise same as Total
   473  	Limit uint64 `json:"limit,omitempty"`
   474  }
   475  
   476  // Get the final system memory limit chosen by the user.
   477  // by default without any configuration on a vanilla Linux
   478  // system you would see physical RAM limit. If cgroup
   479  // is configured at some point in time this function
   480  // would return the memory limit chosen for the given pid.
   481  func getMemoryLimit(sysLimit uint64) uint64 {
   482  	// Following code is deliberately ignoring the error.
   483  	cGroupLimit, err := cgroup.GetMemoryLimit(os.Getpid())
   484  	if err == nil && cGroupLimit <= sysLimit {
   485  		// cgroup limit is lesser than system limit means
   486  		// user wants to limit the memory usage further
   487  		return cGroupLimit
   488  	}
   489  
   490  	return sysLimit
   491  }
   492  
   493  // GetMemInfo returns system's RAM and swap information.
   494  func GetMemInfo(ctx context.Context, addr string) MemInfo {
   495  	meminfo, err := mem.VirtualMemoryWithContext(ctx)
   496  	if err != nil {
   497  		return MemInfo{
   498  			NodeCommon: NodeCommon{
   499  				Addr:  addr,
   500  				Error: err.Error(),
   501  			},
   502  		}
   503  	}
   504  
   505  	swapinfo, err := mem.SwapMemoryWithContext(ctx)
   506  	if err != nil {
   507  		return MemInfo{
   508  			NodeCommon: NodeCommon{
   509  				Addr:  addr,
   510  				Error: err.Error(),
   511  			},
   512  		}
   513  	}
   514  
   515  	return MemInfo{
   516  		NodeCommon:     NodeCommon{Addr: addr},
   517  		Total:          meminfo.Total,
   518  		Available:      meminfo.Available,
   519  		SwapSpaceTotal: swapinfo.Total,
   520  		SwapSpaceFree:  swapinfo.Free,
   521  		Limit:          getMemoryLimit(meminfo.Total),
   522  	}
   523  }
   524  
   525  // ProcInfo contains current process's information.
   526  type ProcInfo struct {
   527  	NodeCommon
   528  
   529  	PID            int32                      `json:"pid,omitempty"`
   530  	IsBackground   bool                       `json:"is_background,omitempty"`
   531  	CPUPercent     float64                    `json:"cpu_percent,omitempty"`
   532  	ChildrenPIDs   []int32                    `json:"children_pids,omitempty"`
   533  	CmdLine        string                     `json:"cmd_line,omitempty"`
   534  	NumConnections int                        `json:"num_connections,omitempty"`
   535  	CreateTime     int64                      `json:"create_time,omitempty"`
   536  	CWD            string                     `json:"cwd,omitempty"`
   537  	ExecPath       string                     `json:"exec_path,omitempty"`
   538  	GIDs           []int32                    `json:"gids,omitempty"`
   539  	IOCounters     process.IOCountersStat     `json:"iocounters,omitempty"`
   540  	IsRunning      bool                       `json:"is_running,omitempty"`
   541  	MemInfo        process.MemoryInfoStat     `json:"mem_info,omitempty"`
   542  	MemMaps        []process.MemoryMapsStat   `json:"mem_maps,omitempty"`
   543  	MemPercent     float32                    `json:"mem_percent,omitempty"`
   544  	Name           string                     `json:"name,omitempty"`
   545  	Nice           int32                      `json:"nice,omitempty"`
   546  	NumCtxSwitches process.NumCtxSwitchesStat `json:"num_ctx_switches,omitempty"`
   547  	NumFDs         int32                      `json:"num_fds,omitempty"`
   548  	NumThreads     int32                      `json:"num_threads,omitempty"`
   549  	PageFaults     process.PageFaultsStat     `json:"page_faults,omitempty"`
   550  	PPID           int32                      `json:"ppid,omitempty"`
   551  	Status         string                     `json:"status,omitempty"`
   552  	TGID           int32                      `json:"tgid,omitempty"`
   553  	Times          cpu.TimesStat              `json:"times,omitempty"`
   554  	UIDs           []int32                    `json:"uids,omitempty"`
   555  	Username       string                     `json:"username,omitempty"`
   556  }
   557  
   558  // GetProcInfo returns current MinIO process information.
   559  func GetProcInfo(ctx context.Context, addr string) ProcInfo {
   560  	pid := int32(syscall.Getpid())
   561  
   562  	procInfo := ProcInfo{
   563  		NodeCommon: NodeCommon{Addr: addr},
   564  		PID:        pid,
   565  	}
   566  	var err error
   567  
   568  	proc, err := process.NewProcess(pid)
   569  	if err != nil {
   570  		procInfo.Error = err.Error()
   571  		return procInfo
   572  	}
   573  
   574  	procInfo.IsBackground, err = proc.BackgroundWithContext(ctx)
   575  	if err != nil {
   576  		procInfo.Error = err.Error()
   577  		return procInfo
   578  	}
   579  
   580  	procInfo.CPUPercent, err = proc.CPUPercentWithContext(ctx)
   581  	if err != nil {
   582  		procInfo.Error = err.Error()
   583  		return procInfo
   584  	}
   585  
   586  	procInfo.ChildrenPIDs = []int32{}
   587  	children, _ := proc.ChildrenWithContext(ctx)
   588  	for i := range children {
   589  		procInfo.ChildrenPIDs = append(procInfo.ChildrenPIDs, children[i].Pid)
   590  	}
   591  
   592  	procInfo.CmdLine, err = proc.CmdlineWithContext(ctx)
   593  	if err != nil {
   594  		procInfo.Error = err.Error()
   595  		return procInfo
   596  	}
   597  
   598  	connections, err := proc.ConnectionsWithContext(ctx)
   599  	if err != nil {
   600  		procInfo.Error = err.Error()
   601  		return procInfo
   602  	}
   603  	procInfo.NumConnections = len(connections)
   604  
   605  	procInfo.CreateTime, err = proc.CreateTimeWithContext(ctx)
   606  	if err != nil {
   607  		procInfo.Error = err.Error()
   608  		return procInfo
   609  	}
   610  
   611  	procInfo.CWD, err = proc.CwdWithContext(ctx)
   612  	if err != nil {
   613  		procInfo.Error = err.Error()
   614  		return procInfo
   615  	}
   616  
   617  	procInfo.ExecPath, err = proc.ExeWithContext(ctx)
   618  	if err != nil {
   619  		procInfo.Error = err.Error()
   620  		return procInfo
   621  	}
   622  
   623  	procInfo.GIDs, err = proc.GidsWithContext(ctx)
   624  	if err != nil {
   625  		procInfo.Error = err.Error()
   626  		return procInfo
   627  	}
   628  
   629  	ioCounters, err := proc.IOCountersWithContext(ctx)
   630  	if err != nil {
   631  		procInfo.Error = err.Error()
   632  		return procInfo
   633  	}
   634  	procInfo.IOCounters = *ioCounters
   635  
   636  	procInfo.IsRunning, err = proc.IsRunningWithContext(ctx)
   637  	if err != nil {
   638  		procInfo.Error = err.Error()
   639  		return procInfo
   640  	}
   641  
   642  	memInfo, err := proc.MemoryInfoWithContext(ctx)
   643  	if err != nil {
   644  		procInfo.Error = err.Error()
   645  		return procInfo
   646  	}
   647  	procInfo.MemInfo = *memInfo
   648  
   649  	memMaps, err := proc.MemoryMapsWithContext(ctx, true)
   650  	if err != nil {
   651  		procInfo.Error = err.Error()
   652  		return procInfo
   653  	}
   654  	procInfo.MemMaps = *memMaps
   655  
   656  	procInfo.MemPercent, err = proc.MemoryPercentWithContext(ctx)
   657  	if err != nil {
   658  		procInfo.Error = err.Error()
   659  		return procInfo
   660  	}
   661  
   662  	procInfo.Name, err = proc.NameWithContext(ctx)
   663  	if err != nil {
   664  		procInfo.Error = err.Error()
   665  		return procInfo
   666  	}
   667  
   668  	procInfo.Nice, err = proc.NiceWithContext(ctx)
   669  	if err != nil {
   670  		procInfo.Error = err.Error()
   671  		return procInfo
   672  	}
   673  
   674  	numCtxSwitches, err := proc.NumCtxSwitchesWithContext(ctx)
   675  	if err != nil {
   676  		procInfo.Error = err.Error()
   677  		return procInfo
   678  	}
   679  	procInfo.NumCtxSwitches = *numCtxSwitches
   680  
   681  	procInfo.NumFDs, err = proc.NumFDsWithContext(ctx)
   682  	if err != nil {
   683  		procInfo.Error = err.Error()
   684  		return procInfo
   685  	}
   686  
   687  	procInfo.NumThreads, err = proc.NumThreadsWithContext(ctx)
   688  	if err != nil {
   689  		procInfo.Error = err.Error()
   690  		return procInfo
   691  	}
   692  
   693  	pageFaults, err := proc.PageFaultsWithContext(ctx)
   694  	if err != nil {
   695  		procInfo.Error = err.Error()
   696  		return procInfo
   697  	}
   698  	procInfo.PageFaults = *pageFaults
   699  
   700  	procInfo.PPID, _ = proc.PpidWithContext(ctx)
   701  
   702  	status, err := proc.StatusWithContext(ctx)
   703  	if err != nil {
   704  		procInfo.Error = err.Error()
   705  		return procInfo
   706  	}
   707  	procInfo.Status = status[0]
   708  
   709  	procInfo.TGID, err = proc.Tgid()
   710  	if err != nil {
   711  		procInfo.Error = err.Error()
   712  		return procInfo
   713  	}
   714  
   715  	times, err := proc.TimesWithContext(ctx)
   716  	if err != nil {
   717  		procInfo.Error = err.Error()
   718  		return procInfo
   719  	}
   720  	procInfo.Times = *times
   721  
   722  	procInfo.UIDs, err = proc.UidsWithContext(ctx)
   723  	if err != nil {
   724  		procInfo.Error = err.Error()
   725  		return procInfo
   726  	}
   727  
   728  	// In certain environments, it is not possible to get username e.g. minio-operator
   729  	// Plus it's not a serious error. So ignore error if any.
   730  	procInfo.Username, err = proc.UsernameWithContext(ctx)
   731  	if err != nil {
   732  		procInfo.Username = "<non-root>"
   733  	}
   734  
   735  	return procInfo
   736  }
   737  
   738  // SysInfo - Includes hardware and system information of the MinIO cluster
   739  type SysInfo struct {
   740  	CPUInfo        []CPUs         `json:"cpus,omitempty"`
   741  	Partitions     []Partitions   `json:"partitions,omitempty"`
   742  	OSInfo         []OSInfo       `json:"osinfo,omitempty"`
   743  	MemInfo        []MemInfo      `json:"meminfo,omitempty"`
   744  	ProcInfo       []ProcInfo     `json:"procinfo,omitempty"`
   745  	SysErrs        []SysErrors    `json:"errors,omitempty"`
   746  	SysServices    []SysServices  `json:"services,omitempty"`
   747  	SysConfig      []SysConfig    `json:"config,omitempty"`
   748  	KubernetesInfo KubernetesInfo `json:"kubernetes"`
   749  }
   750  
   751  // KubernetesInfo - Information about the kubernetes platform
   752  type KubernetesInfo struct {
   753  	Major      string    `json:"major,omitempty"`
   754  	Minor      string    `json:"minor,omitempty"`
   755  	GitVersion string    `json:"gitVersion,omitempty"`
   756  	GitCommit  string    `json:"gitCommit,omitempty"`
   757  	BuildDate  time.Time `json:"buildDate,omitempty"`
   758  	Platform   string    `json:"platform,omitempty"`
   759  	Error      string    `json:"error,omitempty"`
   760  }
   761  
   762  // SpeedTestResults - Includes perf test results of the MinIO cluster
   763  type SpeedTestResults struct {
   764  	DrivePerf []DriveSpeedTestResult `json:"drive,omitempty"`
   765  	ObjPerf   []SpeedTestResult      `json:"obj,omitempty"`
   766  	NetPerf   []NetperfNodeResult    `json:"net,omitempty"`
   767  	Error     string                 `json:"error,omitempty"`
   768  }
   769  
   770  // MinioConfig contains minio configuration of a node.
   771  type MinioConfig struct {
   772  	Error string `json:"error,omitempty"`
   773  
   774  	Config interface{} `json:"config,omitempty"`
   775  }
   776  
   777  // MemStats is strip down version of runtime.MemStats containing memory stats of MinIO server.
   778  type MemStats struct {
   779  	Alloc      uint64
   780  	TotalAlloc uint64
   781  	Mallocs    uint64
   782  	Frees      uint64
   783  	HeapAlloc  uint64
   784  }
   785  
   786  // GCStats collect information about recent garbage collections.
   787  type GCStats struct {
   788  	LastGC     time.Time       `json:"last_gc"`     // time of last collection
   789  	NumGC      int64           `json:"num_gc"`      // number of garbage collections
   790  	PauseTotal time.Duration   `json:"pause_total"` // total pause for all collections
   791  	Pause      []time.Duration `json:"pause"`       // pause history, most recent first
   792  	PauseEnd   []time.Time     `json:"pause_end"`   // pause end times history, most recent first
   793  }
   794  
   795  // ServerInfo holds server information
   796  type ServerInfo struct {
   797  	State          string            `json:"state,omitempty"`
   798  	Endpoint       string            `json:"endpoint,omitempty"`
   799  	Uptime         int64             `json:"uptime,omitempty"`
   800  	Version        string            `json:"version,omitempty"`
   801  	CommitID       string            `json:"commitID,omitempty"`
   802  	Network        map[string]string `json:"network,omitempty"`
   803  	Drives         []Disk            `json:"drives,omitempty"`
   804  	PoolNumber     int               `json:"poolNumber,omitempty"`
   805  	MemStats       MemStats          `json:"mem_stats"`
   806  	GoMaxProcs     int               `json:"go_max_procs"`
   807  	NumCPU         int               `json:"num_cpu"`
   808  	RuntimeVersion string            `json:"runtime_version"`
   809  	GCStats        *GCStats          `json:"gc_stats,omitempty"`
   810  	MinioEnvVars   map[string]string `json:"minio_env_vars,omitempty"`
   811  }
   812  
   813  // MinioInfo contains MinIO server and object storage information.
   814  type MinioInfo struct {
   815  	Mode         string       `json:"mode,omitempty"`
   816  	Domain       []string     `json:"domain,omitempty"`
   817  	Region       string       `json:"region,omitempty"`
   818  	SQSARN       []string     `json:"sqsARN,omitempty"`
   819  	DeploymentID string       `json:"deploymentID,omitempty"`
   820  	Buckets      Buckets      `json:"buckets,omitempty"`
   821  	Objects      Objects      `json:"objects,omitempty"`
   822  	Usage        Usage        `json:"usage,omitempty"`
   823  	Services     Services     `json:"services,omitempty"`
   824  	Backend      interface{}  `json:"backend,omitempty"`
   825  	Servers      []ServerInfo `json:"servers,omitempty"`
   826  	TLS          *TLSInfo     `json:"tls"`
   827  	IsKubernetes *bool        `json:"is_kubernetes"`
   828  	IsDocker     *bool        `json:"is_docker"`
   829  }
   830  
   831  type TLSInfo struct {
   832  	TLSEnabled bool      `json:"tls_enabled"`
   833  	Certs      []TLSCert `json:"certs,omitempty"`
   834  }
   835  
   836  type TLSCert struct {
   837  	PubKeyAlgo    string    `json:"pub_key_algo"`
   838  	SignatureAlgo string    `json:"signature_algo"`
   839  	NotBefore     time.Time `json:"not_before"`
   840  	NotAfter      time.Time `json:"not_after"`
   841  }
   842  
   843  // MinioHealthInfo - Includes MinIO confifuration information
   844  type MinioHealthInfo struct {
   845  	Error string `json:"error,omitempty"`
   846  
   847  	Config MinioConfig `json:"config,omitempty"`
   848  	Info   MinioInfo   `json:"info,omitempty"`
   849  }
   850  
   851  // HealthInfo - MinIO cluster's health Info
   852  type HealthInfo struct {
   853  	Version string `json:"version"`
   854  	Error   string `json:"error,omitempty"`
   855  
   856  	TimeStamp time.Time       `json:"timestamp,omitempty"`
   857  	Sys       SysInfo         `json:"sys,omitempty"`
   858  	Minio     MinioHealthInfo `json:"minio,omitempty"`
   859  }
   860  
   861  func (info HealthInfo) String() string {
   862  	data, err := json.Marshal(info)
   863  	if err != nil {
   864  		panic(err) // This never happens.
   865  	}
   866  	return string(data)
   867  }
   868  
   869  // JSON returns this structure as JSON formatted string.
   870  func (info HealthInfo) JSON() string {
   871  	data, err := json.MarshalIndent(info, " ", "    ")
   872  	if err != nil {
   873  		panic(err) // This never happens.
   874  	}
   875  	return string(data)
   876  }
   877  
   878  // GetError - returns error from the cluster health info
   879  func (info HealthInfo) GetError() string {
   880  	return info.Error
   881  }
   882  
   883  // GetStatus - returns status of the cluster health info
   884  func (info HealthInfo) GetStatus() string {
   885  	if info.Error != "" {
   886  		return "error"
   887  	}
   888  	return "success"
   889  }
   890  
   891  // GetTimestamp - returns timestamp from the cluster health info
   892  func (info HealthInfo) GetTimestamp() time.Time {
   893  	return info.TimeStamp
   894  }
   895  
   896  // HealthDataType - Typed Health data types
   897  type HealthDataType string
   898  
   899  // HealthDataTypes
   900  const (
   901  	HealthDataTypeMinioInfo   HealthDataType = "minioinfo"
   902  	HealthDataTypeMinioConfig HealthDataType = "minioconfig"
   903  	HealthDataTypeSysCPU      HealthDataType = "syscpu"
   904  	HealthDataTypeSysDriveHw  HealthDataType = "sysdrivehw"
   905  	HealthDataTypeSysDocker   HealthDataType = "sysdocker" // is this really needed?
   906  	HealthDataTypeSysOsInfo   HealthDataType = "sysosinfo"
   907  	HealthDataTypeSysLoad     HealthDataType = "sysload" // provides very little info. Making it TBD
   908  	HealthDataTypeSysMem      HealthDataType = "sysmem"
   909  	HealthDataTypeSysNet      HealthDataType = "sysnet"
   910  	HealthDataTypeSysProcess  HealthDataType = "sysprocess"
   911  	HealthDataTypeSysErrors   HealthDataType = "syserrors"
   912  	HealthDataTypeSysServices HealthDataType = "sysservices"
   913  	HealthDataTypeSysConfig   HealthDataType = "sysconfig"
   914  )
   915  
   916  // HealthDataTypesMap - Map of Health datatypes
   917  var HealthDataTypesMap = map[string]HealthDataType{
   918  	"minioinfo":   HealthDataTypeMinioInfo,
   919  	"minioconfig": HealthDataTypeMinioConfig,
   920  	"syscpu":      HealthDataTypeSysCPU,
   921  	"sysdrivehw":  HealthDataTypeSysDriveHw,
   922  	"sysdocker":   HealthDataTypeSysDocker,
   923  	"sysosinfo":   HealthDataTypeSysOsInfo,
   924  	"sysload":     HealthDataTypeSysLoad,
   925  	"sysmem":      HealthDataTypeSysMem,
   926  	"sysnet":      HealthDataTypeSysNet,
   927  	"sysprocess":  HealthDataTypeSysProcess,
   928  	"syserrors":   HealthDataTypeSysErrors,
   929  	"sysservices": HealthDataTypeSysServices,
   930  	"sysconfig":   HealthDataTypeSysConfig,
   931  }
   932  
   933  // HealthDataTypesList - List of health datatypes
   934  var HealthDataTypesList = []HealthDataType{
   935  	HealthDataTypeMinioInfo,
   936  	HealthDataTypeMinioConfig,
   937  	HealthDataTypeSysCPU,
   938  	HealthDataTypeSysDriveHw,
   939  	HealthDataTypeSysDocker,
   940  	HealthDataTypeSysOsInfo,
   941  	HealthDataTypeSysLoad,
   942  	HealthDataTypeSysMem,
   943  	HealthDataTypeSysNet,
   944  	HealthDataTypeSysProcess,
   945  	HealthDataTypeSysErrors,
   946  	HealthDataTypeSysServices,
   947  	HealthDataTypeSysConfig,
   948  }
   949  
   950  // HealthInfoVersionStruct - struct for health info version
   951  type HealthInfoVersionStruct struct {
   952  	Version string `json:"version,omitempty"`
   953  	Error   string `json:"error,omitempty"`
   954  }
   955  
   956  // ServerHealthInfo - Connect to a minio server and call Health Info Management API
   957  // to fetch server's information represented by HealthInfo structure
   958  func (adm *AdminClient) ServerHealthInfo(ctx context.Context, types []HealthDataType, deadline time.Duration) (*http.Response, string, error) {
   959  	v := url.Values{}
   960  	v.Set("deadline", deadline.Truncate(1*time.Second).String())
   961  	for _, d := range HealthDataTypesList { // Init all parameters to false.
   962  		v.Set(string(d), "false")
   963  	}
   964  	for _, d := range types {
   965  		v.Set(string(d), "true")
   966  	}
   967  
   968  	resp, err := adm.executeMethod(
   969  		ctx, "GET", requestData{
   970  			relPath:     adminAPIPrefix + "/healthinfo",
   971  			queryValues: v,
   972  		},
   973  	)
   974  	if err != nil {
   975  		closeResponse(resp)
   976  		return nil, "", err
   977  	}
   978  
   979  	if resp.StatusCode != http.StatusOK {
   980  		closeResponse(resp)
   981  		return nil, "", httpRespToErrorResponse(resp)
   982  	}
   983  
   984  	decoder := json.NewDecoder(resp.Body)
   985  	var version HealthInfoVersionStruct
   986  	if err = decoder.Decode(&version); err != nil {
   987  		closeResponse(resp)
   988  		return nil, "", err
   989  	}
   990  
   991  	if version.Error != "" {
   992  		closeResponse(resp)
   993  		return nil, "", errors.New(version.Error)
   994  	}
   995  
   996  	switch version.Version {
   997  	case "", HealthInfoVersion2, HealthInfoVersion:
   998  	default:
   999  		closeResponse(resp)
  1000  		return nil, "", errors.New("Upgrade Minio Client to support health info version " + version.Version)
  1001  	}
  1002  
  1003  	return resp, version.Version, nil
  1004  }