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 }