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 }