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  }