1// Copyright (c) 2015-2021 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 18package cmd 19 20import ( 21 "fmt" 22 "sort" 23 "strings" 24 "time" 25 26 "github.com/dustin/go-humanize" 27 "github.com/fatih/color" 28 "github.com/minio/cli" 29 json "github.com/minio/colorjson" 30 "github.com/minio/madmin-go" 31 "github.com/minio/mc/pkg/probe" 32 "github.com/minio/pkg/console" 33) 34 35var adminBandwidthInfoCmdFlags = []cli.Flag{ 36 cli.StringFlag{ 37 Name: "unit", 38 Value: "b", 39 Usage: "[b|bi|B|Bi] Display bandwidth in bits (IEC [bi] or SI [b]) or bytes (IEC [Bi] or SI [B])", 40 }, 41} 42 43var adminBwInfoCmd = cli.Command{ 44 Name: "bandwidth", 45 Usage: "Show bandwidth info for buckets on the MinIO server in bits or bytes per second. Ki,Bi,Mi,Gi represent IEC units.", 46 Action: mainAdminBwInfo, 47 Before: setGlobalsFromContext, 48 OnUsageError: onUsageError, 49 Flags: append(globalFlags, adminBandwidthInfoCmdFlags...), 50 CustomHelpTemplate: `NAME: 51 {{.HelpName}} - {{.Usage}} 52 53USAGE: 54 {{.HelpName}} FLAGS TARGET 55 56FLAGS: 57 {{range .VisibleFlags}}{{.}} 58 {{end}} 59EXAMPLES: 60 1. Show the bandwidth usage for all the buckets in a MinIO server setup 61 {{.Prompt}} {{.HelpName}} play/ 62 2. Show the bandwidth usage for the bucket 'source-bucket' in a MinIO server setup 63 {{.Prompt}} {{.HelpName}} play/source-bucket 64`, 65} 66 67func printTable(report madmin.Report, bits bool, iec bool) { 68 bucketMaxLength := 63 69 bucketColLength := 6 70 var bucketKeys []string 71 for bucket := range report.Report.BucketStats { 72 if len(bucket) <= bucketMaxLength && len(bucket) > bucketColLength { 73 bucketColLength = len(bucket) 74 } 75 bucketKeys = append(bucketKeys, bucket) 76 } 77 sort.Strings(bucketKeys) 78 dspOrder := []col{colGreen} // Header 79 for i := 0; i < len(report.Report.BucketStats); i++ { 80 dspOrder = append(dspOrder, colGrey) 81 } 82 var printColors []*color.Color 83 for _, c := range dspOrder { 84 printColors = append(printColors, getPrintCol(c)) 85 } 86 87 cellText := make([][]string, len(report.Report.BucketStats)+1) // 1 for the header 88 tbl := console.NewTable(printColors, []bool{false, false, false}, 0) 89 bucketTitle := fmt.Sprintf("%-16v", "Bucket") 90 cellText[0] = []string{ 91 bucketTitle, 92 "Configured Max Bandwidth", 93 "Current Bandwidth", 94 } 95 tbl.HeaderRowSeparator = true 96 index := 1 97 98 for _, bucket := range bucketKeys { 99 values := report.Report.BucketStats[bucket] 100 if len(bucket) > bucketMaxLength { 101 bucket = bucket[:bucketMaxLength] + ".." 102 } 103 var mul uint64 104 mul = 1 105 if bits { 106 mul = 8 107 } 108 limit := humanize.Bytes(uint64(values.LimitInBytesPerSecond) * mul) 109 current := humanize.Bytes(uint64(values.CurrentBandwidthInBytesPerSecond) * mul) 110 if iec { 111 limit = humanize.IBytes(uint64(values.LimitInBytesPerSecond)*mul) + "/sec" 112 current = humanize.IBytes(uint64(values.CurrentBandwidthInBytesPerSecond)*mul) + "/sec" 113 } 114 if bits { 115 limit = strings.ToLower(limit) + "/sec" 116 current = strings.ToLower(current) + "/sec" 117 } 118 if values.LimitInBytesPerSecond == 0 { 119 limit = "N/A" // N/A means cluster bandwidth is not configured 120 } 121 cellText[index] = []string{ 122 bucket, 123 limit, 124 current, 125 } 126 index++ 127 } 128 if len(report.Report.BucketStats) > 0 { 129 err := tbl.DisplayTable(cellText) 130 if err != nil { 131 console.Error(err) 132 } 133 } 134} 135func checkAdminBwInfoSyntax(ctx *cli.Context) { 136 u := ctx.String("unit") 137 if u != "bi" && 138 u != "b" && 139 u != "Bi" && 140 u != "B" && 141 u != "" { 142 cli.ShowCommandHelpAndExit(ctx, "bandwidth", globalErrorExitStatus) 143 } 144 if len(ctx.Args()) > 1 || len(ctx.Args()) == 0 { 145 cli.ShowCommandHelpAndExit(ctx, "bandwidth", globalErrorExitStatus) 146 } 147} 148 149func mainAdminBwInfo(ctx *cli.Context) { 150 checkAdminBwInfoSyntax(ctx) 151 aliasURL, bucket := getAliasAndBucket(ctx) 152 client := getClient(aliasURL) 153 reportCh := client.GetBucketBandwidth(globalContext, bucket) 154 firstPrint := true 155 bandwidthUnitsString := ctx.String("unit") 156 for { 157 select { 158 case report := <-reportCh: 159 if len(report.Report.BucketStats) == 0 { 160 continue 161 } 162 if report.Err != nil { 163 if strings.Contains(report.Err.Error(), "EOF") { 164 continue 165 } 166 console.Error(report.Err) 167 } 168 printBandwidth(report, firstPrint, bandwidthUnitsString == "bi" || bandwidthUnitsString == "b", 169 bandwidthUnitsString == "bi" || bandwidthUnitsString == "Bi") 170 firstPrint = false 171 case <-globalContext.Done(): 172 return 173 } 174 } 175} 176 177func printBandwidth(report madmin.Report, firstPrint bool, bits bool, iec bool) { 178 rewindLines := len(report.Report.BucketStats) + 4 179 if firstPrint { 180 rewindLines = 0 181 } 182 if globalJSON { 183 reportJSON, e := json.MarshalIndent(report, "", " ") 184 fatalIf(probe.NewError(e), "Unable to marshal to JSON") 185 console.Println(string(reportJSON)) 186 time.Sleep(1 * time.Second) 187 return 188 } 189 if len(report.Report.BucketStats) > 0 { 190 console.RewindLines(rewindLines) 191 // For the next iteration, rewind lines 192 printTable(report, bits, iec) 193 } 194 time.Sleep(1 * time.Second) 195} 196