github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/accounting-reader.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  	"fmt"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/cheggaaa/pb"
    27  	json "github.com/minio/colorjson"
    28  	"github.com/minio/mc/pkg/probe"
    29  )
    30  
    31  // accounter keeps tabs of ongoing data transfer information.
    32  type accounter struct {
    33  	current int64
    34  
    35  	total        int64
    36  	startTime    time.Time
    37  	startValue   int64
    38  	refreshRate  time.Duration
    39  	currentValue int64
    40  	finishOnce   sync.Once
    41  	isFinished   chan struct{}
    42  }
    43  
    44  // Instantiate a new accounter.
    45  func newAccounter(total int64) *accounter {
    46  	acct := &accounter{
    47  		total:        total,
    48  		startTime:    time.Now(),
    49  		startValue:   0,
    50  		refreshRate:  time.Millisecond * 200,
    51  		isFinished:   make(chan struct{}),
    52  		currentValue: -1,
    53  	}
    54  	go acct.writer()
    55  	return acct
    56  }
    57  
    58  // write calculate the final speed.
    59  func (a *accounter) write(current int64) float64 {
    60  	fromStart := time.Since(a.startTime)
    61  	currentFromStart := current - a.startValue
    62  	if currentFromStart > 0 {
    63  		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
    64  		return speed
    65  	}
    66  	return 0.0
    67  }
    68  
    69  // writer update new accounting data for a specified refreshRate.
    70  func (a *accounter) writer() {
    71  	a.Update()
    72  	for {
    73  		select {
    74  		case <-a.isFinished:
    75  			return
    76  		case <-time.After(a.refreshRate):
    77  			a.Update()
    78  		}
    79  	}
    80  }
    81  
    82  // accountStat cantainer for current stats captured.
    83  type accountStat struct {
    84  	Status      string  `json:"status"`
    85  	Total       int64   `json:"total"`
    86  	Transferred int64   `json:"transferred"`
    87  	Speed       float64 `json:"speed"`
    88  }
    89  
    90  func (c accountStat) JSON() string {
    91  	c.Status = "success"
    92  	accountMessageBytes, e := json.MarshalIndent(c, "", " ")
    93  	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
    94  
    95  	return string(accountMessageBytes)
    96  }
    97  
    98  func (c accountStat) String() string {
    99  	speedBox := pb.Format(int64(c.Speed)).To(pb.U_BYTES).String()
   100  	if speedBox == "" {
   101  		speedBox = "0 MB"
   102  	} else {
   103  		speedBox = speedBox + "/s"
   104  	}
   105  	message := fmt.Sprintf("Total: %s, Transferred: %s, Speed: %s", pb.Format(c.Total).To(pb.U_BYTES),
   106  		pb.Format(c.Transferred).To(pb.U_BYTES), speedBox)
   107  	return message
   108  }
   109  
   110  // Stat provides current stats captured.
   111  func (a *accounter) Stat() accountStat {
   112  	var acntStat accountStat
   113  	a.finishOnce.Do(func() {
   114  		close(a.isFinished)
   115  		acntStat.Total = a.total
   116  		acntStat.Transferred = atomic.LoadInt64(&a.current)
   117  		acntStat.Speed = a.write(atomic.LoadInt64(&a.current))
   118  	})
   119  	return acntStat
   120  }
   121  
   122  // Update update with new values loaded atomically.
   123  func (a *accounter) Update() {
   124  	c := atomic.LoadInt64(&a.current)
   125  	if c != a.currentValue {
   126  		a.write(c)
   127  		a.currentValue = c
   128  	}
   129  }
   130  
   131  // Set sets the current value atomically.
   132  func (a *accounter) Set(n int64) *accounter {
   133  	atomic.StoreInt64(&a.current, n)
   134  	return a
   135  }
   136  
   137  // Get gets current value atomically
   138  func (a *accounter) Get() int64 {
   139  	return atomic.LoadInt64(&a.current)
   140  }
   141  
   142  func (a *accounter) SetTotal(n int64) {
   143  	atomic.StoreInt64(&a.total, n)
   144  }
   145  
   146  // Add add to current value atomically.
   147  func (a *accounter) Add(n int64) int64 {
   148  	return atomic.AddInt64(&a.current, n)
   149  }
   150  
   151  // Read implements Reader which internally updates current value.
   152  func (a *accounter) Read(p []byte) (n int, err error) {
   153  	defer func() {
   154  		// Upload retry can read one object twice; Avoid read to be greater than Total
   155  		if n, t := a.Get(), atomic.LoadInt64(&a.total); t > 0 && n > t {
   156  			a.Set(t)
   157  		}
   158  	}()
   159  
   160  	n = len(p)
   161  	a.Add(int64(n))
   162  	return
   163  }