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 }