github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/humanizeutil/humanize.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package humanizeutil
    12  
    13  import (
    14  	"flag"
    15  	"fmt"
    16  	"math"
    17  	"sync/atomic"
    18  	"time"
    19  
    20  	"github.com/cockroachdb/errors"
    21  	"github.com/cockroachdb/redact"
    22  	"github.com/dustin/go-humanize"
    23  	"github.com/spf13/pflag"
    24  )
    25  
    26  // IBytes is an int64 version of go-humanize's IBytes.
    27  func IBytes(value int64) redact.SafeString {
    28  	if value < 0 {
    29  		return redact.SafeString("-" + humanize.IBytes(uint64(-value)))
    30  	}
    31  	return redact.SafeString(humanize.IBytes(uint64(value)))
    32  }
    33  
    34  // ParseBytes is an int64 version of go-humanize's ParseBytes.
    35  func ParseBytes(s string) (int64, error) {
    36  	if len(s) == 0 {
    37  		return 0, errors.New("parsing \"\": invalid syntax")
    38  	}
    39  	var startIndex int
    40  	var negative bool
    41  	if s[0] == '-' {
    42  		negative = true
    43  		startIndex = 1
    44  	}
    45  	value, err := humanize.ParseBytes(s[startIndex:])
    46  	if err != nil {
    47  		return 0, err
    48  	}
    49  	if value > math.MaxInt64 {
    50  		return 0, errors.Errorf("too large: %s", s)
    51  	}
    52  	if negative {
    53  		return -int64(value), nil
    54  	}
    55  	return int64(value), nil
    56  }
    57  
    58  // BytesValue is a struct that implements flag.Value and pflag.Value
    59  // suitable to create command-line parameters that accept sizes
    60  // specified using a format recognized by humanize.
    61  // The value is written atomically, so that it is safe to use this
    62  // struct to make a parameter configurable that is used by an
    63  // asynchronous process spawned before command-line argument handling.
    64  // This is useful e.g. for the log file settings which are used
    65  // by the asynchronous log file GC daemon.
    66  type BytesValue struct {
    67  	val   *int64
    68  	isSet bool
    69  }
    70  
    71  var _ flag.Value = &BytesValue{}
    72  var _ pflag.Value = &BytesValue{}
    73  
    74  // NewBytesValue creates a new pflag.Value bound to the specified
    75  // int64 variable. It also happens to be a flag.Value.
    76  func NewBytesValue(val *int64) *BytesValue {
    77  	return &BytesValue{val: val}
    78  }
    79  
    80  // Set implements the flag.Value and pflag.Value interfaces.
    81  func (b *BytesValue) Set(s string) error {
    82  	v, err := ParseBytes(s)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	if b.val == nil {
    87  		b.val = new(int64)
    88  	}
    89  	atomic.StoreInt64(b.val, v)
    90  	b.isSet = true
    91  	return nil
    92  }
    93  
    94  // Type implements the pflag.Value interface.
    95  func (b *BytesValue) Type() string {
    96  	return "bytes"
    97  }
    98  
    99  // String implements the flag.Value and pflag.Value interfaces.
   100  func (b *BytesValue) String() string {
   101  	return redact.StringWithoutMarkers(b)
   102  }
   103  
   104  // SafeFormat implements the redact.SafeFormatter interface.
   105  func (b *BytesValue) SafeFormat(w redact.SafePrinter, _ rune) {
   106  	// When b.val is nil, the real value of the flag will only be known after a
   107  	// Resolve() call. We do not want our flag package to report an erroneous
   108  	// default value for this flag. So the value we return here must cause
   109  	// defaultIsZeroValue to return true:
   110  	// https://github.com/spf13/pflag/blob/v1.0.5/flag.go#L724
   111  	if b.val == nil {
   112  		w.SafeString("<nil>")
   113  		return
   114  	}
   115  	// This uses the MiB, GiB, etc suffixes. If we use humanize.Bytes() we get
   116  	// the MB, GB, etc suffixes, but the conversion is done in multiples of 1000
   117  	// vs 1024.
   118  	w.Print(IBytes(atomic.LoadInt64(b.val)))
   119  }
   120  
   121  // IsSet returns true iff Set has successfully been called.
   122  func (b *BytesValue) IsSet() bool {
   123  	return b.isSet
   124  }
   125  
   126  // DataRate formats the passed byte count over duration as "x MiB/s".
   127  func DataRate(bytes int64, elapsed time.Duration) redact.SafeString {
   128  	if bytes == 0 {
   129  		return "0"
   130  	}
   131  	if elapsed == 0 {
   132  		return "inf"
   133  	}
   134  	return redact.SafeString(fmt.Sprintf("%0.2f MiB/s",
   135  		(float64(bytes)/elapsed.Seconds())/float64(1<<20)))
   136  }