1
0
mirror of https://github.com/minio/mc.git synced 2025-11-10 13:42:32 +03:00
Files
mc/cmd/admin-info-server.go

380 lines
11 KiB
Go

/*
* MinIO Client (C) 2016-2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"fmt"
"net/url"
"sort"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/fatih/color"
"github.com/minio/cli"
json "github.com/minio/mc/pkg/colorjson"
"github.com/minio/mc/pkg/console"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio/pkg/madmin"
)
var adminInfoServer = cli.Command{
Name: "server",
Usage: "display MinIO server information",
Action: mainAdminServerInfo,
Before: setGlobalsFromContext,
Flags: globalFlags,
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} TARGET
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES:
1. Get server information of the 'play' MinIO server.
$ {{.HelpName}} play/
`,
}
// backendType - indicates the type of backend storage
type backendType string
const (
fsType = backendType("FS")
erasureType = backendType("Erasure")
)
// fsBackend contains specific FS storage information
type fsBackend struct {
Type backendType `json:"backendType"`
}
// xlBackend contains specific erasure storage information
type xlBackend struct {
Type backendType `json:"backendType"`
OnlineDisks int `json:"onlineDisks"`
OfflineDisks int `json:"offlineDisks"`
// Data disks for currently configured Standard storage class.
StandardSCData int `json:"standardSCData"`
// Parity disks for currently configured Standard storage class.
StandardSCParity int `json:"standardSCParity"`
// Data disks for currently configured Reduced Redundancy storage class.
RRSCData int `json:"rrSCData"`
// Parity disks for currently configured Reduced Redundancy storage class.
RRSCParity int `json:"rrSCParity"`
// List of all disk status.
Sets [][]madmin.DriveInfo `json:"sets"`
}
// backendStatus represents the overall information of all backend storage types
type backendStatus struct {
// Total used space per tenant.
Used uint64 `json:"used"`
// Total available space.
Available uint64 `json:"available"`
// Total disk space.
Total uint64 `json:"total"`
// Backend type.
Backend interface{} `json:"backend"`
}
// ServerInfo holds the whole server information that will be
// returned by ServerInfo API.
type ServerInfo struct {
StorageInfo backendStatus `json:"storage"`
ConnStats madmin.ServerConnStats `json:"network"`
Properties madmin.ServerProperties `json:"server"`
CPULoad madmin.ServerCPULoadInfo `json:"cpu,omitempty"`
MemUsage madmin.ServerMemUsageInfo `json:"mem,omitempty"`
}
// infoMessage container to hold service status information.
type infoMessage struct {
Status string `json:"status"`
Service string `json:"service"`
Addr string `json:"address"`
Err string `json:"error"`
*ServerInfo
}
// String colorized service status message.
func (u infoMessage) String() (msg string) {
dot := "●"
// When service is offline
if u.Service == "off" {
msg += fmt.Sprintf("%s %s\n", console.Colorize("InfoFail", dot), console.Colorize("PrintB", u.Addr))
msg += fmt.Sprintf(" Uptime: %s\n", console.Colorize("InfoFail", "offline"))
return
}
// Print error if any and exit
if u.Err != "" {
msg += fmt.Sprintf("%s %s\n", console.Colorize("InfoFail", dot), console.Colorize("PrintB", u.Addr))
msg += fmt.Sprintf(" Uptime: %s\n", console.Colorize("InfoFail", "offline"))
e := u.Err
if strings.Trim(e, " ") == "rpc: retry error" {
e = "unreachable"
}
msg += fmt.Sprintf(" Error: %s", console.Colorize("InfoFail", e))
return
}
// Print server title
msg += fmt.Sprintf("%s %s\n", console.Colorize("Info", dot), console.Colorize("PrintB", u.Addr))
// Uptime
msg += fmt.Sprintf(" Uptime: %s\n", console.Colorize("Info",
humanize.RelTime(time.Now(), time.Now().Add(-u.ServerInfo.Properties.Uptime), "", "")))
// Version
version := u.ServerInfo.Properties.Version
if u.ServerInfo.Properties.Version == "DEVELOPMENT.GOGET" {
version = "<development>"
}
msg += fmt.Sprintf(" Version: %s\n", version)
// Region
if u.ServerInfo.Properties.Region != "" {
msg += fmt.Sprintf(" Region: %s\n", u.ServerInfo.Properties.Region)
}
// ARNs
sqsARNs := ""
for _, v := range u.ServerInfo.Properties.SQSARN {
sqsARNs += fmt.Sprintf("%s ", v)
}
if sqsARNs != "" {
msg += fmt.Sprintf(" SQS ARNs: %s\n", sqsARNs)
}
// Incoming/outgoing
msg += fmt.Sprintf(" Storage: Used %s, Free %s",
humanize.IBytes(u.StorageInfo.Used),
humanize.IBytes(u.StorageInfo.Available))
if v, ok := u.ServerInfo.StorageInfo.Backend.(xlBackend); ok {
upBackends := 0
downBackends := 0
for _, set := range v.Sets {
for _, s := range set {
if strings.Contains(s.Endpoint, u.Addr) || s.Endpoint[0] == '/' || s.Endpoint[0] == '.' {
if s.State == "ok" {
upBackends++
} else {
downBackends++
}
}
}
}
upBackendsString := fmt.Sprintf("%d", upBackends)
if downBackends != 0 {
upBackendsString = console.Colorize("InfoFail", fmt.Sprintf("%d", upBackends))
}
msg += fmt.Sprintf("\n Drives: %s/%d %s\n", upBackendsString,
upBackends+downBackends, console.Colorize("Info", "OK"))
msg += "\n"
//CPU section
msg += fmt.Sprintf("%s min avg max\n", console.Colorize("Info", " CPU"))
for i := range u.CPULoad.Load {
msg += fmt.Sprintf(" current %.2f%% %.2f%% %.2f%%\n", u.CPULoad.Load[i].Min, u.CPULoad.Load[i].Avg, u.CPULoad.Load[i].Max)
if len(u.CPULoad.HistoricLoad) > i {
msg += fmt.Sprintf(" historic %.2f%% %.2f%% %.2f%%\n", u.CPULoad.HistoricLoad[i].Min, u.CPULoad.HistoricLoad[i].Avg, u.CPULoad.HistoricLoad[i].Max)
}
msg += "\n"
}
// Mem section
msg += fmt.Sprintf("%s usage\n", console.Colorize("Info", " MEM"))
for i := range u.MemUsage.Usage {
msg += fmt.Sprintf(" current %s\n", humanize.IBytes(u.MemUsage.Usage[i].Mem))
if len(u.MemUsage.HistoricUsage) > i {
msg += fmt.Sprintf(" historic %s\n", humanize.IBytes(u.MemUsage.HistoricUsage[i].Mem))
}
msg += "\n"
}
}
return
}
// JSON jsonified service status message.
func (u infoMessage) JSON() string {
u.Status = "success"
statusJSONBytes, e := json.MarshalIndent(u, "", " ")
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
return string(statusJSONBytes)
}
// checkAdminInfoSyntax - validate all the passed arguments
func checkAdminServerInfoSyntax(ctx *cli.Context) {
if len(ctx.Args()) == 0 || len(ctx.Args()) > 2 {
cli.ShowCommandHelpAndExit(ctx, "server", 1) // last argument is exit code
}
}
func mainAdminServerInfo(ctx *cli.Context) error {
checkAdminServerInfoSyntax(ctx)
console.SetColor("Info", color.New(color.FgGreen, color.Bold))
console.SetColor("InfoDegraded", color.New(color.FgYellow, color.Bold))
console.SetColor("InfoFail", color.New(color.FgRed, color.Bold))
// Get the alias parameter from cli
args := ctx.Args()
aliasedURL := args.Get(0)
// Create a new MinIO Admin Client
client, err := newAdminClient(aliasedURL)
fatalIf(err, "Unable to initialize admin connection.")
printOfflineErrorMessage := func(err error) {
errMsg := ""
if err != nil {
errMsg = err.Error()
}
printMsg(infoMessage{
Addr: aliasedURL,
Service: "off",
Err: errMsg,
})
}
processErr := func(e error) error {
switch e.(type) {
case *json.SyntaxError:
printOfflineErrorMessage(e)
return e
case *url.Error:
printOfflineErrorMessage(e)
return e
default:
// If the error is not nil and unrecognized, just print it and exit
fatalIf(probe.NewError(e), "Cannot get service status.")
}
return nil
}
// Fetch info of all servers (cluster or single server)
serversInfo, e := client.ServerInfo()
if err := processErr(e); err != nil {
// exit immediately if error encountered
return nil
}
// Fetch info of all CPU loads (all MinIO server instances)
cpuLoads, e := client.ServerCPULoadInfo()
if err := processErr(e); err != nil {
// exit immediately if error encountered
return nil
}
memUsages, e := client.ServerMemUsageInfo()
if err := processErr(e); err != nil {
// exit immediately if error encountered
return nil
}
infoMessages := []infoMessage{}
for i, serverInfo := range serversInfo {
cpuLoad := cpuLoads[i]
memUsage := memUsages[i]
// Print the error if exists and jump to the next server
if serverInfo.Error != "" {
infoMessages = append(infoMessages, infoMessage{
Service: "on",
Addr: serverInfo.Addr,
Err: serverInfo.Error,
})
continue
}
// Construct the backend status
storageInfo := backendStatus{
Used: serverInfo.Data.StorageInfo.Used,
Available: serverInfo.Data.StorageInfo.Available,
Total: serverInfo.Data.StorageInfo.Total,
}
if serverInfo.Data.StorageInfo.Backend.Type == madmin.Erasure {
storageInfo.Backend = xlBackend{
Type: erasureType,
OnlineDisks: serverInfo.Data.StorageInfo.Backend.OnlineDisks,
OfflineDisks: serverInfo.Data.StorageInfo.Backend.OfflineDisks,
StandardSCData: serverInfo.Data.StorageInfo.Backend.StandardSCData,
StandardSCParity: serverInfo.Data.StorageInfo.Backend.StandardSCParity,
RRSCData: serverInfo.Data.StorageInfo.Backend.RRSCData,
RRSCParity: serverInfo.Data.StorageInfo.Backend.RRSCParity,
Sets: serverInfo.Data.StorageInfo.Backend.Sets,
}
} else {
storageInfo.Backend = fsBackend{
Type: fsType,
}
}
infoMessages = append(infoMessages, (infoMessage{
Service: "on",
Addr: serverInfo.Addr,
Err: serverInfo.Error,
ServerInfo: &ServerInfo{
StorageInfo: storageInfo,
ConnStats: serverInfo.Data.ConnStats,
Properties: serverInfo.Data.Properties,
CPULoad: cpuLoad,
MemUsage: memUsage,
},
}))
}
sort.Stable(&sortInfoWrapper{infoMessages})
for _, s := range infoMessages {
printMsg(s)
}
return nil
}
type sortInfoWrapper struct {
infos []infoMessage
}
func (s *sortInfoWrapper) Len() int {
return len(s.infos)
}
func (s *sortInfoWrapper) Swap(i, j int) {
if s.infos != nil {
s.infos[i], s.infos[j] = s.infos[j], s.infos[i]
}
}
func (s *sortInfoWrapper) Less(i, j int) bool {
if s.infos != nil {
return s.infos[i].Addr < s.infos[j].Addr
}
return false
}