github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/ilm-tier-info.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"fmt"
    22  	"strconv"
    23  
    24  	"github.com/charmbracelet/lipgloss"
    25  	"github.com/charmbracelet/lipgloss/table"
    26  	"github.com/dustin/go-humanize"
    27  	"github.com/minio/cli"
    28  	json "github.com/minio/colorjson"
    29  	"github.com/minio/madmin-go/v3"
    30  	"github.com/minio/pkg/v2/console"
    31  )
    32  
    33  var adminTierInfoCmd = cli.Command{
    34  	Name:         "info",
    35  	Usage:        "display tier statistics",
    36  	Action:       mainAdminTierInfo,
    37  	OnUsageError: onUsageError,
    38  	Before:       setGlobalsFromContext,
    39  	Flags:        globalFlags,
    40  	CustomHelpTemplate: `NAME:
    41    {{.HelpName}} - {{.Usage}}
    42  
    43  USAGE:
    44    {{.HelpName}} ALIAS [NAME]
    45  
    46  FLAGS:
    47    {{range .VisibleFlags}}{{.}}
    48    {{end}}
    49  
    50  EXAMPLES:
    51    1. Prints per-tier statistics of all remote tier targets configured on 'myminio':
    52       {{.Prompt}} {{.HelpName}} myminio
    53  
    54    2. Print per-tier statistics of given tier name 'MINIOTIER-1':
    55       {{.Prompt}} {{.HelpName}} myminio MINIOTIER-1
    56  `,
    57  }
    58  
    59  // checkAdminTierInfoSyntax - validate all the passed arguments
    60  func checkAdminTierInfoSyntax(ctx *cli.Context) {
    61  	argsNr := len(ctx.Args())
    62  	if argsNr < 1 {
    63  		showCommandHelpAndExit(ctx, 1) // last argument is exit code
    64  	}
    65  	if argsNr == 2 && globalJSON {
    66  		fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...),
    67  			"Incorrect number of arguments for tier-info subcommand with json output.")
    68  	}
    69  	if argsNr > 2 {
    70  		fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...),
    71  			"Incorrect number of arguments for tier-info subcommand.")
    72  	}
    73  }
    74  
    75  type tierInfos []madmin.TierInfo
    76  
    77  var _ table.Data = tierInfos(nil)
    78  
    79  func (t tierInfos) At(row, col int) string {
    80  	cell := "-"
    81  	switch col {
    82  	case 0:
    83  		cell = t[row].Name
    84  	case 1:
    85  		cell = t[row].Type
    86  	case 2:
    87  		cell = tierInfoType(t[row].Type)
    88  	case 3:
    89  		cell = humanize.IBytes(t[row].Stats.TotalSize)
    90  	case 4:
    91  		cell = strconv.Itoa(t[row].Stats.NumObjects)
    92  	case 5:
    93  		cell = strconv.Itoa(t[row].Stats.NumVersions)
    94  	}
    95  	return cell
    96  }
    97  
    98  func (t tierInfos) Rows() int {
    99  	return len(t)
   100  }
   101  
   102  func (t tierInfos) Columns() int {
   103  	return len(t.Headers())
   104  }
   105  
   106  func (t tierInfos) Headers() []string {
   107  	return []string{
   108  		"Tier Name",
   109  		"API",
   110  		"Type",
   111  		"Usage",
   112  		"Objects",
   113  		"Versions",
   114  	}
   115  }
   116  
   117  func (t tierInfos) MarshalJSON() ([]byte, error) {
   118  	type tierInfo struct {
   119  		Name       string
   120  		API        string
   121  		Type       string
   122  		Stats      madmin.TierStats
   123  		DailyStats madmin.DailyTierStats
   124  	}
   125  	ts := make([]tierInfo, 0, len(t))
   126  	for _, tInfo := range t {
   127  		ts = append(ts, tierInfo{
   128  			Name:       tInfo.Name,
   129  			API:        tInfo.Type,
   130  			Type:       tierInfoType(tInfo.Type),
   131  			Stats:      tInfo.Stats,
   132  			DailyStats: tInfo.DailyStats,
   133  		})
   134  	}
   135  	return json.Marshal(ts)
   136  }
   137  
   138  func tierInfoType(tierType string) string {
   139  	if tierType == "internal" {
   140  		return "hot"
   141  	}
   142  	return "warm"
   143  }
   144  
   145  func mainAdminTierInfo(ctx *cli.Context) error {
   146  	checkAdminTierInfoSyntax(ctx)
   147  	args := ctx.Args()
   148  	aliasedURL := args.Get(0)
   149  	tier := args.Get(1)
   150  
   151  	// Create a new MinIO Admin Client
   152  	client, cerr := newAdminClient(aliasedURL)
   153  	fatalIf(cerr, "Unable to initialize admin connection.")
   154  
   155  	var msg tierInfoMessage
   156  	tInfos, e := client.TierStats(globalContext)
   157  	if e != nil {
   158  		msg = tierInfoMessage{
   159  			Status:  "error",
   160  			Context: ctx,
   161  			Error:   e.Error(),
   162  		}
   163  	} else {
   164  		msg = tierInfoMessage{
   165  			Status:    "success",
   166  			Context:   ctx,
   167  			TierInfos: tierInfos(tInfos),
   168  		}
   169  	}
   170  
   171  	if globalJSON {
   172  		printMsg(&msg)
   173  		return nil
   174  	}
   175  
   176  	var (
   177  		HeaderStyle  = lipgloss.NewStyle().Bold(true).Align(lipgloss.Center)
   178  		EvenRowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Align(lipgloss.Center)
   179  		OddRowStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("4")).Align(lipgloss.Center)
   180  		NumbersStyle = lipgloss.NewStyle().Align(lipgloss.Right)
   181  	)
   182  	tableData := tierInfos(tInfos)
   183  	filteredData := table.NewFilter(tableData).
   184  		Filter(func(row int) bool {
   185  			if tier == "" {
   186  				return true
   187  			}
   188  			return tableData.At(row, 0) == tier
   189  		})
   190  	tbl := table.New().
   191  		Border(lipgloss.NormalBorder()).
   192  		Headers(tableData.Headers()...).
   193  		StyleFunc(func(row, col int) lipgloss.Style {
   194  			var style lipgloss.Style
   195  			switch {
   196  			case row == 0:
   197  				return HeaderStyle
   198  			case row%2 == 0:
   199  				style = EvenRowStyle
   200  			default:
   201  				style = OddRowStyle
   202  			}
   203  			switch col {
   204  			case 3, 4, 5:
   205  				style = NumbersStyle.Foreground(style.GetForeground())
   206  			}
   207  			return style
   208  		}).
   209  		Data(filteredData)
   210  
   211  	if filteredData.Rows() == 0 {
   212  		if tier != "" {
   213  			console.Printf("No remote tiers' name match %s\n", tier)
   214  		} else {
   215  			console.Println("No remote tiers configured")
   216  		}
   217  		return nil
   218  	}
   219  	fmt.Println(tbl)
   220  
   221  	return nil
   222  }
   223  
   224  type tierInfoMessage struct {
   225  	Status    string       `json:"status"`
   226  	Context   *cli.Context `json:"-"`
   227  	TierInfos tierInfos    `json:"tiers,omitempty"`
   228  	Error     string       `json:"error,omitempty"`
   229  }
   230  
   231  // String method returns a tabular listing of remote tier configurations.
   232  func (msg *tierInfoMessage) String() string {
   233  	return "" // Not used, present to satisfy msg interface
   234  }
   235  
   236  // JSON method returns JSON encoding of msg.
   237  func (msg *tierInfoMessage) JSON() string {
   238  	b, _ := json.Marshal(msg)
   239  	return string(b)
   240  }