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 }