github.com/minio/madmin-go/v2@v2.2.1/health.go (about)

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