github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/summary/collector.go (about)

     1  // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0.
     2  
     3  package summary
     4  
     5  import (
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/docker/go-units"
    11  
    12  	"github.com/pingcap/log"
    13  	"go.uber.org/zap"
    14  )
    15  
    16  const (
    17  	// BackupUnit tells summary in backup
    18  	BackupUnit = "backup"
    19  	// RestoreUnit tells summary in restore
    20  	RestoreUnit = "restore"
    21  
    22  	// TotalKV is a field we collect during backup/restore
    23  	TotalKV = "total kv"
    24  	// TotalBytes is a field we collect during backup/restore
    25  	TotalBytes = "total bytes"
    26  	// BackupDataSize is a field we collect after backup finish
    27  	BackupDataSize = "backup data size(after compressed)"
    28  	// RestoreDataSize is a field we collection after restore finish
    29  	RestoreDataSize = "restore data size(after compressed)"
    30  )
    31  
    32  // LogCollector collects infos into summary log.
    33  type LogCollector interface {
    34  	SetUnit(unit string)
    35  
    36  	CollectSuccessUnit(name string, unitCount int, arg interface{})
    37  
    38  	CollectFailureUnit(name string, reason error)
    39  
    40  	CollectDuration(name string, t time.Duration)
    41  
    42  	CollectInt(name string, t int)
    43  
    44  	CollectUInt(name string, t uint64)
    45  
    46  	SetSuccessStatus(success bool)
    47  
    48  	Summary(name string)
    49  }
    50  
    51  type logFunc func(msg string, fields ...zap.Field)
    52  
    53  var collector LogCollector = NewLogCollector(log.Info)
    54  
    55  // InitCollector initilize global collector instance.
    56  func InitCollector( // revive:disable-line:flag-parameter
    57  	hasLogFile bool,
    58  ) {
    59  	logF := log.L().Info
    60  	if hasLogFile {
    61  		conf := new(log.Config)
    62  		// Always duplicate summary to stdout.
    63  		logger, _, err := log.InitLogger(conf)
    64  		if err == nil {
    65  			logF = func(msg string, fields ...zap.Field) {
    66  				logger.Info(msg, fields...)
    67  				log.Info(msg, fields...)
    68  			}
    69  		}
    70  	}
    71  	collector = NewLogCollector(logF)
    72  }
    73  
    74  type logCollector struct {
    75  	mu               sync.Mutex
    76  	unit             string
    77  	successUnitCount int
    78  	failureUnitCount int
    79  	successCosts     map[string]time.Duration
    80  	successData      map[string]uint64
    81  	failureReasons   map[string]error
    82  	durations        map[string]time.Duration
    83  	ints             map[string]int
    84  	uints            map[string]uint64
    85  	successStatus    bool
    86  	startTime        time.Time
    87  
    88  	log logFunc
    89  }
    90  
    91  // NewLogCollector returns a new LogCollector.
    92  func NewLogCollector(log logFunc) LogCollector {
    93  	return &logCollector{
    94  		successUnitCount: 0,
    95  		failureUnitCount: 0,
    96  		successCosts:     make(map[string]time.Duration),
    97  		successData:      make(map[string]uint64),
    98  		failureReasons:   make(map[string]error),
    99  		durations:        make(map[string]time.Duration),
   100  		ints:             make(map[string]int),
   101  		uints:            make(map[string]uint64),
   102  		log:              log,
   103  		startTime:        time.Now(),
   104  	}
   105  }
   106  
   107  func (tc *logCollector) SetUnit(unit string) {
   108  	tc.mu.Lock()
   109  	defer tc.mu.Unlock()
   110  	tc.unit = unit
   111  }
   112  
   113  func (tc *logCollector) CollectSuccessUnit(name string, unitCount int, arg interface{}) {
   114  	tc.mu.Lock()
   115  	defer tc.mu.Unlock()
   116  
   117  	switch v := arg.(type) {
   118  	case time.Duration:
   119  		tc.successUnitCount += unitCount
   120  		tc.successCosts[name] += v
   121  	case uint64:
   122  		tc.successData[name] += v
   123  	}
   124  }
   125  
   126  func (tc *logCollector) CollectFailureUnit(name string, reason error) {
   127  	tc.mu.Lock()
   128  	defer tc.mu.Unlock()
   129  	if _, ok := tc.failureReasons[name]; !ok {
   130  		tc.failureReasons[name] = reason
   131  		tc.failureUnitCount++
   132  	}
   133  }
   134  
   135  func (tc *logCollector) CollectDuration(name string, t time.Duration) {
   136  	tc.mu.Lock()
   137  	defer tc.mu.Unlock()
   138  	tc.durations[name] += t
   139  }
   140  
   141  func (tc *logCollector) CollectInt(name string, t int) {
   142  	tc.mu.Lock()
   143  	defer tc.mu.Unlock()
   144  	tc.ints[name] += t
   145  }
   146  
   147  func (tc *logCollector) CollectUInt(name string, t uint64) {
   148  	tc.mu.Lock()
   149  	defer tc.mu.Unlock()
   150  	tc.uints[name] += t
   151  }
   152  
   153  func (tc *logCollector) SetSuccessStatus(success bool) {
   154  	tc.mu.Lock()
   155  	defer tc.mu.Unlock()
   156  	tc.successStatus = success
   157  }
   158  
   159  func logKeyFor(key string) string {
   160  	return strings.ReplaceAll(key, " ", "-")
   161  }
   162  
   163  func (tc *logCollector) Summary(name string) {
   164  	tc.mu.Lock()
   165  	defer func() {
   166  		tc.durations = make(map[string]time.Duration)
   167  		tc.ints = make(map[string]int)
   168  		tc.successCosts = make(map[string]time.Duration)
   169  		tc.failureReasons = make(map[string]error)
   170  		tc.mu.Unlock()
   171  	}()
   172  
   173  	logFields := make([]zap.Field, 0, len(tc.durations)+len(tc.ints)+3)
   174  
   175  	logFields = append(logFields,
   176  		zap.Int("total-ranges", tc.failureUnitCount+tc.successUnitCount),
   177  		zap.Int("ranges-succeed", tc.successUnitCount),
   178  		zap.Int("ranges-failed", tc.failureUnitCount),
   179  	)
   180  
   181  	for key, val := range tc.durations {
   182  		logFields = append(logFields, zap.Duration(logKeyFor(key), val))
   183  	}
   184  	for key, val := range tc.ints {
   185  		logFields = append(logFields, zap.Int(logKeyFor(key), val))
   186  	}
   187  	for key, val := range tc.uints {
   188  		logFields = append(logFields, zap.Uint64(logKeyFor(key), val))
   189  	}
   190  
   191  	if len(tc.failureReasons) != 0 || !tc.successStatus {
   192  		for unitName, reason := range tc.failureReasons {
   193  			logFields = append(logFields, zap.String("unit-name", unitName), zap.Error(reason))
   194  		}
   195  		tc.log(name+" failed summary", logFields...)
   196  		return
   197  	}
   198  
   199  	totalDureTime := time.Since(tc.startTime)
   200  	logFields = append(logFields, zap.Duration("total-take", totalDureTime))
   201  	for name, data := range tc.successData {
   202  		if name == TotalBytes {
   203  			logFields = append(logFields,
   204  				zap.String("total-kv-size", units.HumanSize(float64(data))),
   205  				zap.String("average-speed", units.HumanSize(float64(data)/totalDureTime.Seconds())+"/s"))
   206  			continue
   207  		}
   208  		if name == BackupDataSize {
   209  			if tc.failureUnitCount+tc.successUnitCount == 0 {
   210  				logFields = append(logFields, zap.String("Result", "Nothing to bakcup"))
   211  			} else {
   212  				logFields = append(logFields,
   213  					zap.String(BackupDataSize, units.HumanSize(float64(data))))
   214  			}
   215  			continue
   216  		}
   217  		if name == RestoreDataSize {
   218  			if tc.failureUnitCount+tc.successUnitCount == 0 {
   219  				logFields = append(logFields, zap.String("Result", "Nothing to restore"))
   220  			} else {
   221  				logFields = append(logFields,
   222  					zap.String(RestoreDataSize, units.HumanSize(float64(data))))
   223  			}
   224  			continue
   225  		}
   226  		logFields = append(logFields, zap.Uint64(logKeyFor(name), data))
   227  	}
   228  
   229  	tc.log(name+" success summary", logFields...)
   230  }
   231  
   232  // SetLogCollector allow pass LogCollector outside.
   233  func SetLogCollector(l LogCollector) {
   234  	collector = l
   235  }