github.com/KinWaiYuen/client-go/v2@v2.5.4/util/misc.go (about)

     1  // Copyright 2021 TiKV Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // NOTE: The code in this file is based on code from the
    16  // TiDB project, licensed under the Apache License v 2.0
    17  //
    18  // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/util/misc.go
    19  //
    20  
    21  // Copyright 2021 PingCAP, Inc.
    22  //
    23  // Licensed under the Apache License, Version 2.0 (the "License");
    24  // you may not use this file except in compliance with the License.
    25  // You may obtain a copy of the License at
    26  //
    27  //     http://www.apache.org/licenses/LICENSE-2.0
    28  //
    29  // Unless required by applicable law or agreed to in writing, software
    30  // distributed under the License is distributed on an "AS IS" BASIS,
    31  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    32  // See the License for the specific language governing permissions and
    33  // limitations under the License.
    34  
    35  package util
    36  
    37  import (
    38  	"context"
    39  	"fmt"
    40  	"strconv"
    41  	"strings"
    42  	"time"
    43  
    44  	"github.com/KinWaiYuen/client-go/v2/internal/logutil"
    45  	"github.com/pingcap/errors"
    46  	"go.uber.org/zap"
    47  )
    48  
    49  // GCTimeFormat is the format that gc_worker used to store times.
    50  const GCTimeFormat = "20060102-15:04:05 -0700"
    51  
    52  // CompatibleParseGCTime parses a string with `GCTimeFormat` and returns a time.Time. If `value` can't be parsed as that
    53  // format, truncate to last space and try again. This function is only useful when loading times that saved by
    54  // gc_worker. We have changed the format that gc_worker saves time (removed the last field), but when loading times it
    55  // should be compatible with the old format.
    56  func CompatibleParseGCTime(value string) (time.Time, error) {
    57  	t, err := time.Parse(GCTimeFormat, value)
    58  
    59  	if err != nil {
    60  		// Remove the last field that separated by space
    61  		parts := strings.Split(value, " ")
    62  		prefix := strings.Join(parts[:len(parts)-1], " ")
    63  		t, err = time.Parse(GCTimeFormat, prefix)
    64  	}
    65  
    66  	if err != nil {
    67  		err = errors.Errorf("string \"%v\" doesn't has a prefix that matches format \"%v\"", value, GCTimeFormat)
    68  	}
    69  	return t, err
    70  }
    71  
    72  // WithRecovery wraps goroutine startup call with force recovery.
    73  // it will dump current goroutine stack into log if catch any recover result.
    74  //   exec:      execute logic function.
    75  //   recoverFn: handler will be called after recover and before dump stack, passing `nil` means noop.
    76  func WithRecovery(exec func(), recoverFn func(r interface{})) {
    77  	defer func() {
    78  		r := recover()
    79  		if recoverFn != nil {
    80  			recoverFn(r)
    81  		}
    82  		if r != nil {
    83  			logutil.BgLogger().Error("panic in the recoverable goroutine",
    84  				zap.Reflect("r", r),
    85  				zap.Stack("stack trace"))
    86  		}
    87  	}()
    88  	exec()
    89  }
    90  
    91  type sessionIDCtxKey struct{}
    92  
    93  // SessionID is the context key type to mark a session.
    94  var SessionID = sessionIDCtxKey{}
    95  
    96  // SetSessionID sets session id into context
    97  func SetSessionID(ctx context.Context, sessionID uint64) context.Context {
    98  	return context.WithValue(ctx, SessionID, sessionID)
    99  }
   100  
   101  const (
   102  	byteSizeGB = int64(1 << 30)
   103  	byteSizeMB = int64(1 << 20)
   104  	byteSizeKB = int64(1 << 10)
   105  	byteSizeBB = int64(1)
   106  )
   107  
   108  // FormatBytes uses to format bytes, this function will prune precision before format bytes.
   109  func FormatBytes(numBytes int64) string {
   110  	if numBytes <= byteSizeKB {
   111  		return BytesToString(numBytes)
   112  	}
   113  	unit, unitStr := getByteUnit(numBytes)
   114  	if unit == byteSizeBB {
   115  		return BytesToString(numBytes)
   116  	}
   117  	v := float64(numBytes) / float64(unit)
   118  	decimal := 1
   119  	if numBytes%unit == 0 {
   120  		decimal = 0
   121  	} else if v < 10 {
   122  		decimal = 2
   123  	}
   124  	return fmt.Sprintf("%v %s", strconv.FormatFloat(v, 'f', decimal, 64), unitStr)
   125  }
   126  
   127  func getByteUnit(b int64) (int64, string) {
   128  	if b > byteSizeGB {
   129  		return byteSizeGB, "GB"
   130  	} else if b > byteSizeMB {
   131  		return byteSizeMB, "MB"
   132  	} else if b > byteSizeKB {
   133  		return byteSizeKB, "KB"
   134  	}
   135  	return byteSizeBB, "Bytes"
   136  }
   137  
   138  // BytesToString converts the memory consumption to a readable string.
   139  func BytesToString(numBytes int64) string {
   140  	GB := float64(numBytes) / float64(byteSizeGB)
   141  	if GB > 1 {
   142  		return fmt.Sprintf("%v GB", GB)
   143  	}
   144  
   145  	MB := float64(numBytes) / float64(byteSizeMB)
   146  	if MB > 1 {
   147  		return fmt.Sprintf("%v MB", MB)
   148  	}
   149  
   150  	KB := float64(numBytes) / float64(byteSizeKB)
   151  	if KB > 1 {
   152  		return fmt.Sprintf("%v KB", KB)
   153  	}
   154  
   155  	return fmt.Sprintf("%v Bytes", numBytes)
   156  }