github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/od-main.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 "context" 22 "fmt" 23 "strings" 24 "time" 25 26 json "github.com/minio/colorjson" 27 28 humanize "github.com/dustin/go-humanize" 29 "github.com/minio/cli" 30 "github.com/minio/mc/pkg/probe" 31 ) 32 33 // make a bucket. 34 var odCmd = cli.Command{ 35 Name: "od", 36 Usage: "measure single stream upload and download", 37 Action: mainOD, 38 Before: setGlobalsFromContext, 39 OnUsageError: onUsageError, 40 Flags: globalFlags, 41 CustomHelpTemplate: `NAME: 42 {{.HelpName}} - {{.Usage}} 43 44 USAGE: 45 {{.HelpName}} [OPERANDS] 46 47 OPERANDS: 48 if= source stream to upload 49 of= target path to upload to 50 size= size of each part. If not specified, will be calculated from the source stream size. 51 parts= number of parts to upload. If not specified, will calculated from the source file size. 52 skip= number of parts to skip. 53 {{if .VisibleFlags}} 54 FLAGS: 55 {{range .VisibleFlags}}{{.}} 56 {{end}}{{end}} 57 EXAMPLES: 58 1. Upload 200MiB of a file to a bucket in 5 parts of size 40MiB. 59 {{.HelpName}} if=file.txt of=play/my-bucket/file.txt size=40MiB parts=5 60 61 2. Upload a full file to a bucket with 40MiB parts. 62 {{.HelpName}} if=file.txt of=play/my-bucket/file.txt size=40MiB 63 64 3. Upload a full file to a bucket in 5 parts. 65 {{.HelpName}} if=file.txt of=play/my-bucket/file.txt parts=5 66 `, 67 } 68 69 type odMessage struct { 70 Status string `json:"status"` 71 Type string `json:"type"` 72 Source string `json:"source"` 73 Target string `json:"target"` 74 PartSize uint64 `json:"partSize"` 75 TotalSize int64 `json:"totalSize"` 76 Parts int `json:"parts"` 77 Skip int `json:"skip"` 78 Elapsed int64 `json:"elapsed"` 79 } 80 81 func (o odMessage) String() string { 82 cleanSize := humanize.IBytes(uint64(o.TotalSize)) 83 elapsed := time.Duration(o.Elapsed) * time.Millisecond 84 speed := humanize.IBytes(uint64(float64(o.TotalSize) / elapsed.Seconds())) 85 if o.Type == "S3toFS" && o.Parts == 0 { 86 return fmt.Sprintf("Transferred: %s, Full file, Time: %s, Speed: %s/s", cleanSize, elapsed, speed) 87 } 88 return fmt.Sprintf("Transferred: %s, Parts: %d, Time: %s, Speed: %s/s", cleanSize, o.Parts, elapsed, speed) 89 } 90 91 func (o odMessage) JSON() string { 92 odMessageBytes, e := json.MarshalIndent(o, "", " ") 93 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 94 95 return string(odMessageBytes) 96 } 97 98 // getOdUrls returns the URLs for the object download. 99 func getOdUrls(ctx context.Context, args argKVS) (odURLs URLs, e error) { 100 inFile := args.Get("if") 101 outFile := args.Get("of") 102 103 // Check if outFile is a folder or a file. 104 opts := prepareCopyURLsOpts{ 105 sourceURLs: []string{inFile}, 106 targetURL: outFile, 107 } 108 copyURLsContent, err := guessCopyURLType(ctx, opts) 109 fatalIf(err, "Unable to guess copy URL type") 110 111 // Get content of inFile, set up URLs. 112 switch copyURLsContent.copyType { 113 case copyURLsTypeA: 114 odURLs = makeCopyContentTypeA(*copyURLsContent) 115 case copyURLsTypeB: 116 return URLs{}, fmt.Errorf("invalid source path %s, destination cannot be a directory", outFile) 117 default: 118 return URLs{}, fmt.Errorf("invalid source path %s, source cannot be a directory", inFile) 119 } 120 121 return odURLs, nil 122 } 123 124 // odCheckType checks if request is a download or upload and calls the appropriate function 125 func odCheckType(ctx context.Context, odURLs URLs, args argKVS) (message, error) { 126 if odURLs.SourceAlias != "" && odURLs.TargetAlias == "" { 127 return odDownload(ctx, odURLs, args) 128 } 129 130 var odType string 131 if odURLs.SourceAlias == "" && odURLs.TargetAlias != "" { 132 odType = "FStoS3" 133 } else if odURLs.SourceAlias != "" && odURLs.TargetAlias != "" { 134 odType = "S3toS3" 135 } else { 136 odType = "FStoFS" 137 } 138 return odCopy(ctx, odURLs, args, odType) 139 } 140 141 // mainOd is the entry point for the od command. 142 func mainOD(cliCtx *cli.Context) error { 143 ctx, cancelCopy := context.WithCancel(globalContext) 144 defer cancelCopy() 145 146 if !cliCtx.Args().Present() { 147 showCommandHelpAndExit(cliCtx, 1) // last argument is exit code 148 } 149 150 var kvsArgs argKVS 151 for _, arg := range cliCtx.Args() { 152 kv := strings.SplitN(arg, "=", 2) 153 kvsArgs.Set(kv[0], kv[1]) 154 } 155 156 // Get content from source. 157 odURLs, e := getOdUrls(ctx, kvsArgs) 158 fatalIf(probe.NewError(e), "Unable to get source and target URLs") 159 160 message, e := odCheckType(ctx, odURLs, kvsArgs) 161 fatalIf(probe.NewError(e), "Unable to transfer object") 162 163 // Print message. 164 printMsg(message) 165 return nil 166 }