Refactoring: ewma speed decorator
Vladimir Bauer
8 years ago
| 57 | 57 | removeOnComplete bool |
| 58 | 58 | barClearOnComplete bool |
| 59 | 59 | completeFlushed bool |
| 60 | startTime time.Time | |
| 61 | timeElapsed time.Duration | |
| 62 | 60 | aDecorators []decor.Decorator |
| 63 | 61 | pDecorators []decor.Decorator |
| 64 | 62 | amountReceivers []decor.AmountReceiver |
| 139 | 137 | } |
| 140 | 138 | |
| 141 | 139 | // ProxyReader wrapper for io operations, like io.Copy |
| 142 | func (b *Bar) ProxyReader(r io.Reader, startBlock ...chan<- time.Time) *Reader { | |
| 140 | // | |
| 141 | // `r` io.Reader to be wrapped | |
| 142 | // | |
| 143 | // `sbChannels` optional start block channels | |
| 144 | func (b *Bar) ProxyReader(r io.Reader, sbChannels ...chan<- time.Time) *Reader { | |
| 143 | 145 | proxyReader := &Reader{ |
| 144 | Reader: r, | |
| 145 | bar: b, | |
| 146 | } | |
| 147 | if len(startBlock) > 0 { | |
| 148 | proxyReader.startBlockCh = startBlock[0] | |
| 146 | Reader: r, | |
| 147 | bar: b, | |
| 148 | sbChannels: sbChannels, | |
| 149 | 149 | } |
| 150 | 150 | return proxyReader |
| 151 | 151 | } |
| 250 | 250 | for _, ar := range s.amountReceivers { |
| 251 | 251 | ar.NextAmount(n) |
| 252 | 252 | } |
| 253 | s.timeElapsed = time.Since(s.startTime) | |
| 254 | 253 | }: |
| 255 | 254 | case <-b.done: |
| 256 | 255 | } |
| 265 | 264 | |
| 266 | 265 | func (b *Bar) serve(wg *sync.WaitGroup, s *bState, cancel <-chan struct{}) { |
| 267 | 266 | defer wg.Done() |
| 268 | s.startTime = time.Now() | |
| 269 | 267 | for { |
| 270 | 268 | select { |
| 271 | 269 | case op := <-b.operateState: |
| 414 | 412 | |
| 415 | 413 | func newStatistics(s *bState) *decor.Statistics { |
| 416 | 414 | return &decor.Statistics{ |
| 417 | ID: s.id, | |
| 418 | Completed: s.completeFlushed, | |
| 419 | Total: s.total, | |
| 420 | Current: s.current, | |
| 421 | StartTime: s.startTime, | |
| 422 | TimeElapsed: s.timeElapsed, | |
| 415 | ID: s.id, | |
| 416 | Completed: s.completeFlushed, | |
| 417 | Total: s.total, | |
| 418 | Current: s.current, | |
| 423 | 419 | } |
| 424 | 420 | } |
| 425 | 421 | |
| 134 | 134 | |
| 135 | 135 | io.WriteString(st, res) |
| 136 | 136 | } |
| 137 | ||
| 138 | // CountersNoUnit returns raw counters decorator | |
| 139 | // | |
| 140 | // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" | |
| 141 | // | |
| 142 | // `wcc` optional WC config | |
| 143 | func CountersNoUnit(pairFormat string, wcc ...WC) Decorator { | |
| 144 | return counters(0, pairFormat, wcc...) | |
| 145 | } | |
| 146 | ||
| 147 | // CountersKibiByte returns human friendly byte counters decorator, where counters unit is multiple by 1024. | |
| 148 | // | |
| 149 | // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" | |
| 150 | // | |
| 151 | // `wcc` optional WC config | |
| 152 | // | |
| 153 | // pairFormat example: | |
| 154 | // | |
| 155 | // "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" | |
| 156 | func CountersKibiByte(pairFormat string, wcc ...WC) Decorator { | |
| 157 | return counters(unitKiB, pairFormat, wcc...) | |
| 158 | } | |
| 159 | ||
| 160 | // CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000. | |
| 161 | // | |
| 162 | // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" | |
| 163 | // | |
| 164 | // `wcc` optional WC config | |
| 165 | // | |
| 166 | // pairFormat example: | |
| 167 | // | |
| 168 | // "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" | |
| 169 | func CountersKiloByte(pairFormat string, wcc ...WC) Decorator { | |
| 170 | return counters(unitKB, pairFormat, wcc...) | |
| 171 | } | |
| 172 | ||
| 173 | func counters(unit int, pairFormat string, wcc ...WC) Decorator { | |
| 174 | var wc WC | |
| 175 | for _, widthConf := range wcc { | |
| 176 | wc = widthConf | |
| 177 | } | |
| 178 | wc.BuildFormat() | |
| 179 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 180 | var str string | |
| 181 | switch unit { | |
| 182 | case unitKiB: | |
| 183 | str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total)) | |
| 184 | case unitKB: | |
| 185 | str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total)) | |
| 186 | default: | |
| 187 | str = fmt.Sprintf(pairFormat, s.Current, s.Total) | |
| 188 | } | |
| 189 | return wc.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 190 | }) | |
| 191 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "unicode/utf8" | |
| 5 | ) | |
| 6 | ||
| 7 | const ( | |
| 8 | // DidentRight bit specifies identation direction. | |
| 9 | // |foo |b | With DidentRight | |
| 10 | // | foo| b| Without DidentRight | |
| 11 | DidentRight = 1 << iota | |
| 12 | ||
| 13 | // DextraSpace bit adds extra space, makes sense with DSyncWidth only. | |
| 14 | // When DidentRight bit set, the space will be added to the right, | |
| 15 | // otherwise to the left. | |
| 16 | DextraSpace | |
| 17 | ||
| 18 | // DSyncWidth bit enables same column width synchronization. | |
| 19 | // Effective with multiple bars only. | |
| 20 | DSyncWidth | |
| 21 | ||
| 22 | // DSyncWidthR is shortcut for DSyncWidth|DidentRight | |
| 23 | DSyncWidthR = DSyncWidth | DidentRight | |
| 24 | ||
| 25 | // DSyncSpace is shortcut for DSyncWidth|DextraSpace | |
| 26 | DSyncSpace = DSyncWidth | DextraSpace | |
| 27 | ||
| 28 | // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight | |
| 29 | DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight | |
| 30 | ) | |
| 31 | ||
| 32 | const ( | |
| 33 | ET_STYLE_GO = iota | |
| 34 | ET_STYLE_HHMMSS | |
| 35 | ET_STYLE_HHMM | |
| 36 | ET_STYLE_MMSS | |
| 37 | ) | |
| 38 | ||
| 39 | // Statistics is a struct, which gets passed to a Decorator. | |
| 40 | type Statistics struct { | |
| 41 | ID int | |
| 42 | Completed bool | |
| 43 | Total int64 | |
| 44 | Current int64 | |
| 45 | } | |
| 46 | ||
| 47 | // Decorator is an interface with one method: | |
| 48 | // | |
| 49 | // Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string | |
| 50 | // | |
| 51 | // All decorators in this package implement this interface. | |
| 52 | type Decorator interface { | |
| 53 | Decor(*Statistics, chan<- int, <-chan int) string | |
| 54 | } | |
| 55 | ||
| 56 | // OnCompleteMessenger is an interface with one method: | |
| 57 | // | |
| 58 | // OnCompleteMessage(message string, wc ...WC) | |
| 59 | // | |
| 60 | // Decorators implementing this interface suppose to return provided string on complete event. | |
| 61 | type OnCompleteMessenger interface { | |
| 62 | OnCompleteMessage(string, ...WC) | |
| 63 | } | |
| 64 | ||
| 65 | type AmountReceiver interface { | |
| 66 | NextAmount(int) | |
| 67 | } | |
| 68 | ||
| 69 | type ShutdownListener interface { | |
| 70 | Shutdown() | |
| 71 | } | |
| 72 | ||
| 73 | // DecoratorFunc is an adapter for Decorator interface | |
| 74 | type DecoratorFunc func(*Statistics, chan<- int, <-chan int) string | |
| 75 | ||
| 76 | func (f DecoratorFunc) Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 77 | return f(s, widthAccumulator, widthDistributor) | |
| 78 | } | |
| 79 | ||
| 80 | // Global convenience shortcuts | |
| 81 | var ( | |
| 82 | WCSyncWidth = WC{C: DSyncWidth} | |
| 83 | WCSyncWidthR = WC{C: DSyncWidthR} | |
| 84 | WCSyncSpace = WC{C: DSyncSpace} | |
| 85 | WCSyncSpaceR = WC{C: DSyncSpaceR} | |
| 86 | ) | |
| 87 | ||
| 88 | // WC is a struct with two public fields W and C, both of int type. | |
| 89 | // W represents width and C represents bit set of width related config. | |
| 90 | type WC struct { | |
| 91 | W int | |
| 92 | C int | |
| 93 | format string | |
| 94 | } | |
| 95 | ||
| 96 | // FormatMsg formats final message according to WC.W and WC.C. | |
| 97 | // Should be called by any Decorator implementation. | |
| 98 | func (wc WC) FormatMsg(msg string, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 99 | if (wc.C & DSyncWidth) != 0 { | |
| 100 | widthAccumulator <- utf8.RuneCountInString(msg) | |
| 101 | max := <-widthDistributor | |
| 102 | if max == 0 { | |
| 103 | max = wc.W | |
| 104 | } | |
| 105 | if (wc.C & DextraSpace) != 0 { | |
| 106 | max++ | |
| 107 | } | |
| 108 | return fmt.Sprintf(fmt.Sprintf(wc.format, max), msg) | |
| 109 | } | |
| 110 | return fmt.Sprintf(fmt.Sprintf(wc.format, wc.W), msg) | |
| 111 | } | |
| 112 | ||
| 113 | // BuildFormat builds initial format according to WC.C | |
| 114 | func (wc *WC) BuildFormat() { | |
| 115 | wc.format = "%%" | |
| 116 | if (wc.C & DidentRight) != 0 { | |
| 117 | wc.format += "-" | |
| 118 | } | |
| 119 | wc.format += "%ds" | |
| 120 | } | |
| 121 | ||
| 122 | // OnComplete returns decorator, which wraps provided decorator, with sole | |
| 123 | // purpose to display provided message on complete event. | |
| 124 | // | |
| 125 | // `decorator` Decorator to wrap | |
| 126 | // | |
| 127 | // `message` message to display on complete event | |
| 128 | // | |
| 129 | // `wcc` optional WC config | |
| 130 | func OnComplete(decorator Decorator, message string, wcc ...WC) Decorator { | |
| 131 | if cm, ok := decorator.(OnCompleteMessenger); ok { | |
| 132 | cm.OnCompleteMessage(message, wcc...) | |
| 133 | return decorator | |
| 134 | } | |
| 135 | msgDecorator := Name(message, wcc...) | |
| 136 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 137 | if s.Completed { | |
| 138 | return msgDecorator.Decor(s, widthAccumulator, widthDistributor) | |
| 139 | } | |
| 140 | return decorator.Decor(s, widthAccumulator, widthDistributor) | |
| 141 | }) | |
| 142 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "math" | |
| 5 | "time" | |
| 6 | "unicode/utf8" | |
| 7 | ) | |
| 8 | ||
| 9 | const ( | |
| 10 | // DidentRight bit specifies identation direction. | |
| 11 | // |foo |b | With DidentRight | |
| 12 | // | foo| b| Without DidentRight | |
| 13 | DidentRight = 1 << iota | |
| 14 | ||
| 15 | // DextraSpace bit adds extra space, makes sense with DSyncWidth only. | |
| 16 | // When DidentRight bit set, the space will be added to the right, | |
| 17 | // otherwise to the left. | |
| 18 | DextraSpace | |
| 19 | ||
| 20 | // DSyncWidth bit enables same column width synchronization. | |
| 21 | // Effective with multiple bars only. | |
| 22 | DSyncWidth | |
| 23 | ||
| 24 | // DSyncWidthR is shortcut for DSyncWidth|DidentRight | |
| 25 | DSyncWidthR = DSyncWidth | DidentRight | |
| 26 | ||
| 27 | // DSyncSpace is shortcut for DSyncWidth|DextraSpace | |
| 28 | DSyncSpace = DSyncWidth | DextraSpace | |
| 29 | ||
| 30 | // DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight | |
| 31 | DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight | |
| 32 | ) | |
| 33 | ||
| 34 | const ( | |
| 35 | ET_STYLE_GO = iota | |
| 36 | ET_STYLE_HHMMSS | |
| 37 | ET_STYLE_HHMM | |
| 38 | ET_STYLE_MMSS | |
| 39 | ) | |
| 40 | ||
| 41 | // Statistics is a struct, which gets passed to a Decorator. | |
| 42 | type Statistics struct { | |
| 43 | ID int | |
| 44 | Completed bool | |
| 45 | Total int64 | |
| 46 | Current int64 | |
| 47 | StartTime time.Time | |
| 48 | TimeElapsed time.Duration | |
| 49 | } | |
| 50 | ||
| 51 | // Decorator is an interface with one method: | |
| 52 | // | |
| 53 | // Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string | |
| 54 | // | |
| 55 | // All decorators in this package implement this interface. | |
| 56 | type Decorator interface { | |
| 57 | Decor(*Statistics, chan<- int, <-chan int) string | |
| 58 | } | |
| 59 | ||
| 60 | // OnCompleteMessenger is an interface with one method: | |
| 61 | // | |
| 62 | // OnCompleteMessage(message string, wc ...WC) | |
| 63 | // | |
| 64 | // Decorators implementing this interface suppose to return provided string on complete event. | |
| 65 | type OnCompleteMessenger interface { | |
| 66 | OnCompleteMessage(string, ...WC) | |
| 67 | } | |
| 68 | ||
| 69 | type AmountReceiver interface { | |
| 70 | NextAmount(int) | |
| 71 | } | |
| 72 | ||
| 73 | type ShutdownListener interface { | |
| 74 | Shutdown() | |
| 75 | } | |
| 76 | ||
| 77 | // DecoratorFunc is an adapter for Decorator interface | |
| 78 | type DecoratorFunc func(*Statistics, chan<- int, <-chan int) string | |
| 79 | ||
| 80 | func (f DecoratorFunc) Decor(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 81 | return f(s, widthAccumulator, widthDistributor) | |
| 82 | } | |
| 83 | ||
| 84 | // WC is a struct with two public fields W and C, both of int type. | |
| 85 | // W represents width and C represents bit set of width related config. | |
| 86 | type WC struct { | |
| 87 | W int | |
| 88 | C int | |
| 89 | format string | |
| 90 | } | |
| 91 | ||
| 92 | // FormatMsg formats final message according to WC.W and WC.C. | |
| 93 | // Should be called by any Decorator implementation. | |
| 94 | func (wc WC) FormatMsg(msg string, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 95 | if (wc.C & DSyncWidth) != 0 { | |
| 96 | widthAccumulator <- utf8.RuneCountInString(msg) | |
| 97 | max := <-widthDistributor | |
| 98 | if max == 0 { | |
| 99 | max = wc.W | |
| 100 | } | |
| 101 | if (wc.C & DextraSpace) != 0 { | |
| 102 | max++ | |
| 103 | } | |
| 104 | return fmt.Sprintf(fmt.Sprintf(wc.format, max), msg) | |
| 105 | } | |
| 106 | return fmt.Sprintf(fmt.Sprintf(wc.format, wc.W), msg) | |
| 107 | } | |
| 108 | ||
| 109 | // BuildFormat builds initial format according to WC.C | |
| 110 | func (wc *WC) BuildFormat() { | |
| 111 | wc.format = "%%" | |
| 112 | if (wc.C & DidentRight) != 0 { | |
| 113 | wc.format += "-" | |
| 114 | } | |
| 115 | wc.format += "%ds" | |
| 116 | } | |
| 117 | ||
| 118 | // Global convenience shortcuts | |
| 119 | var ( | |
| 120 | WCSyncWidth = WC{C: DSyncWidth} | |
| 121 | WCSyncWidthR = WC{C: DSyncWidthR} | |
| 122 | WCSyncSpace = WC{C: DSyncSpace} | |
| 123 | WCSyncSpaceR = WC{C: DSyncSpaceR} | |
| 124 | ) | |
| 125 | ||
| 126 | // OnComplete returns decorator, which wraps provided decorator, with sole | |
| 127 | // purpose to display provided message on complete event. | |
| 128 | // | |
| 129 | // `decorator` Decorator to wrap | |
| 130 | // | |
| 131 | // `message` message to display on complete event | |
| 132 | // | |
| 133 | // `wc` optional WC config | |
| 134 | func OnComplete(decorator Decorator, message string, wc ...WC) Decorator { | |
| 135 | if cm, ok := decorator.(OnCompleteMessenger); ok { | |
| 136 | cm.OnCompleteMessage(message, wc...) | |
| 137 | return decorator | |
| 138 | } | |
| 139 | msgDecorator := Name(message, wc...) | |
| 140 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 141 | if s.Completed { | |
| 142 | return msgDecorator.Decor(s, widthAccumulator, widthDistributor) | |
| 143 | } | |
| 144 | return decorator.Decor(s, widthAccumulator, widthDistributor) | |
| 145 | }) | |
| 146 | } | |
| 147 | ||
| 148 | // StaticName returns name decorator. | |
| 149 | // | |
| 150 | // `name` string to display | |
| 151 | // | |
| 152 | // `wc` optional WC config | |
| 153 | func StaticName(name string, wc ...WC) Decorator { | |
| 154 | return Name(name, wc...) | |
| 155 | } | |
| 156 | ||
| 157 | // Name returns name decorator. | |
| 158 | // | |
| 159 | // `name` string to display | |
| 160 | // | |
| 161 | // `wc` optional WC config | |
| 162 | func Name(name string, wc ...WC) Decorator { | |
| 163 | var wc0 WC | |
| 164 | if len(wc) > 0 { | |
| 165 | wc0 = wc[0] | |
| 166 | } | |
| 167 | wc0.BuildFormat() | |
| 168 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 169 | return wc0.FormatMsg(name, widthAccumulator, widthDistributor) | |
| 170 | }) | |
| 171 | } | |
| 172 | ||
| 173 | // CountersNoUnit returns raw counters decorator | |
| 174 | // | |
| 175 | // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" | |
| 176 | // | |
| 177 | // `wc` optional WC config | |
| 178 | func CountersNoUnit(pairFormat string, wc ...WC) Decorator { | |
| 179 | return counters(pairFormat, 0, wc...) | |
| 180 | } | |
| 181 | ||
| 182 | // CountersKibiByte returns human friendly byte counters decorator, where counters unit is multiple by 1024. | |
| 183 | // | |
| 184 | // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" | |
| 185 | // | |
| 186 | // `wc` optional WC config | |
| 187 | // | |
| 188 | // pairFormat example: | |
| 189 | // | |
| 190 | // "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" | |
| 191 | func CountersKibiByte(pairFormat string, wc ...WC) Decorator { | |
| 192 | return counters(pairFormat, unitKiB, wc...) | |
| 193 | } | |
| 194 | ||
| 195 | // CountersKiloByte returns human friendly byte counters decorator, where counters unit is multiple by 1000. | |
| 196 | // | |
| 197 | // `pairFormat` printf compatible verbs for current and total, like "%f" or "%d" | |
| 198 | // | |
| 199 | // `wc` optional WC config | |
| 200 | // | |
| 201 | // pairFormat example: | |
| 202 | // | |
| 203 | // "%.1f / %.1f" = "1.0MB / 12.0MB" or "% .1f / % .1f" = "1.0 MB / 12.0 MB" | |
| 204 | func CountersKiloByte(pairFormat string, wc ...WC) Decorator { | |
| 205 | return counters(pairFormat, unitKB, wc...) | |
| 206 | } | |
| 207 | ||
| 208 | func counters(pairFormat string, unit int, wc ...WC) Decorator { | |
| 209 | var wc0 WC | |
| 210 | if len(wc) > 0 { | |
| 211 | wc0 = wc[0] | |
| 212 | } | |
| 213 | wc0.BuildFormat() | |
| 214 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 215 | var str string | |
| 216 | switch unit { | |
| 217 | case unitKiB: | |
| 218 | str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total)) | |
| 219 | case unitKB: | |
| 220 | str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total)) | |
| 221 | default: | |
| 222 | str = fmt.Sprintf(pairFormat, s.Current, s.Total) | |
| 223 | } | |
| 224 | return wc0.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 225 | }) | |
| 226 | } | |
| 227 | ||
| 228 | // Elapsed returns elapsed time decorator. | |
| 229 | // | |
| 230 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] | |
| 231 | // | |
| 232 | // `wc` optional WC config | |
| 233 | func Elapsed(style int, wc ...WC) Decorator { | |
| 234 | var wc0 WC | |
| 235 | if len(wc) > 0 { | |
| 236 | wc0 = wc[0] | |
| 237 | } | |
| 238 | wc0.BuildFormat() | |
| 239 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 240 | var str string | |
| 241 | hours := int64((s.TimeElapsed / time.Hour) % 60) | |
| 242 | minutes := int64((s.TimeElapsed / time.Minute) % 60) | |
| 243 | seconds := int64((s.TimeElapsed / time.Second) % 60) | |
| 244 | ||
| 245 | switch style { | |
| 246 | case ET_STYLE_GO: | |
| 247 | str = fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) | |
| 248 | case ET_STYLE_HHMMSS: | |
| 249 | str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) | |
| 250 | case ET_STYLE_HHMM: | |
| 251 | str = fmt.Sprintf("%02d:%02d", hours, minutes) | |
| 252 | case ET_STYLE_MMSS: | |
| 253 | str = fmt.Sprintf("%02d:%02d", minutes, seconds) | |
| 254 | } | |
| 255 | return wc0.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 256 | }) | |
| 257 | } | |
| 258 | ||
| 259 | // Percentage returns percentage decorator. | |
| 260 | // | |
| 261 | // `wc` optional WC config | |
| 262 | func Percentage(wc ...WC) Decorator { | |
| 263 | var wc0 WC | |
| 264 | if len(wc) > 0 { | |
| 265 | wc0 = wc[0] | |
| 266 | } | |
| 267 | wc0.BuildFormat() | |
| 268 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 269 | str := fmt.Sprintf("%d %%", CalcPercentage(s.Total, s.Current, 100)) | |
| 270 | return wc0.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 271 | }) | |
| 272 | } | |
| 273 | ||
| 274 | // CalcPercentage is a helper function, to calculate percentage. | |
| 275 | func CalcPercentage(total, current, width int64) int64 { | |
| 276 | if total <= 0 { | |
| 277 | return 0 | |
| 278 | } | |
| 279 | p := float64(width*current) / float64(total) | |
| 280 | return int64(round(p)) | |
| 281 | } | |
| 282 | ||
| 283 | // SpeedNoUnit returns raw I/O operation speed decorator. | |
| 284 | // | |
| 285 | // `unitFormat` printf compatible verb for value, like "%f" or "%d" | |
| 286 | // | |
| 287 | // `wc` optional WC config | |
| 288 | // | |
| 289 | // unitFormat example: | |
| 290 | // | |
| 291 | // "%.1f" = "1.0" or "% .1f" = "1.0" | |
| 292 | func SpeedNoUnit(unitFormat string, wc ...WC) Decorator { | |
| 293 | return speed(unitFormat, 0, wc...) | |
| 294 | } | |
| 295 | ||
| 296 | // SpeedKibiByte returns human friendly I/O operation speed decorator, | |
| 297 | // | |
| 298 | // `unitFormat` printf compatible verb for value, like "%f" or "%d" | |
| 299 | // | |
| 300 | // `wc` optional WC config | |
| 301 | // | |
| 302 | // unitFormat example: | |
| 303 | // | |
| 304 | // "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" | |
| 305 | func SpeedKibiByte(unitFormat string, wc ...WC) Decorator { | |
| 306 | return speed(unitFormat, unitKiB, wc...) | |
| 307 | } | |
| 308 | ||
| 309 | // SpeedKiloByte returns human friendly I/O operation speed decorator, | |
| 310 | // | |
| 311 | // `unitFormat` printf compatible verb for value, like "%f" or "%d" | |
| 312 | // | |
| 313 | // `wc` optional WC config | |
| 314 | // | |
| 315 | // unitFormat example: | |
| 316 | // | |
| 317 | // "%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s" | |
| 318 | func SpeedKiloByte(unitFormat string, wc ...WC) Decorator { | |
| 319 | return speed(unitFormat, unitKB, wc...) | |
| 320 | } | |
| 321 | ||
| 322 | func speed(unitFormat string, unit int, wc ...WC) Decorator { | |
| 323 | var wc0 WC | |
| 324 | if len(wc) > 0 { | |
| 325 | wc0 = wc[0] | |
| 326 | } | |
| 327 | wc0.BuildFormat() | |
| 328 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 329 | var str string | |
| 330 | speed := float64(s.Current) / s.TimeElapsed.Seconds() | |
| 331 | if math.IsNaN(speed) || math.IsInf(speed, 0) { | |
| 332 | speed = .0 | |
| 333 | } | |
| 334 | ||
| 335 | switch unit { | |
| 336 | case unitKiB: | |
| 337 | str = fmt.Sprintf(unitFormat, SpeedKiB(speed)) | |
| 338 | case unitKB: | |
| 339 | str = fmt.Sprintf(unitFormat, SpeedKB(speed)) | |
| 340 | default: | |
| 341 | str = fmt.Sprintf(unitFormat, speed) | |
| 342 | } | |
| 343 | return wc0.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 344 | }) | |
| 345 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import ( | |
| 3 | "fmt" | |
| 4 | "time" | |
| 5 | ) | |
| 6 | ||
| 7 | // Elapsed returns elapsed time decorator. | |
| 8 | // | |
| 9 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] | |
| 10 | // | |
| 11 | // `wcc` optional WC config | |
| 12 | func Elapsed(style int, wcc ...WC) Decorator { | |
| 13 | var wc WC | |
| 14 | for _, widthConf := range wcc { | |
| 15 | wc = widthConf | |
| 16 | } | |
| 17 | wc.BuildFormat() | |
| 18 | startTime := time.Now() | |
| 19 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 20 | var str string | |
| 21 | timeElapsed := time.Since(startTime) | |
| 22 | hours := int64((timeElapsed / time.Hour) % 60) | |
| 23 | minutes := int64((timeElapsed / time.Minute) % 60) | |
| 24 | seconds := int64((timeElapsed / time.Second) % 60) | |
| 25 | ||
| 26 | switch style { | |
| 27 | case ET_STYLE_GO: | |
| 28 | str = fmt.Sprint(time.Duration(timeElapsed.Seconds()) * time.Second) | |
| 29 | case ET_STYLE_HHMMSS: | |
| 30 | str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) | |
| 31 | case ET_STYLE_HHMM: | |
| 32 | str = fmt.Sprintf("%02d:%02d", hours, minutes) | |
| 33 | case ET_STYLE_MMSS: | |
| 34 | str = fmt.Sprintf("%02d:%02d", minutes, seconds) | |
| 35 | } | |
| 36 | return wc.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 37 | }) | |
| 38 | } |
| 10 | 10 | // |
| 11 | 11 | // `style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS] |
| 12 | 12 | // |
| 13 | // `age` is related to the decay factor alpha by the formula given for the DECAY constant. | |
| 14 | // It signifies the average age of the samples as time goes to infinity. Basically age is | |
| 15 | // the previous N samples to average over. If zero value provided, it defaults to 30. | |
| 13 | // `age` is the previous N samples to average over. | |
| 14 | // If zero value provided, it defaults to 30. | |
| 16 | 15 | // |
| 17 | // `startBlock` is a time.Time receive channel. User suppose to send time.Now() | |
| 18 | // to this channel on each iteration of block start, right before actual job. | |
| 19 | // The channel will be closed automatically on bar shutdown event, so there is | |
| 20 | // no need to close from user side. | |
| 16 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 17 | // to this channel on each iteration of a start block, right before actual job. | |
| 18 | // The channel will be auto closed on bar shutdown event, so there is no need | |
| 19 | // to close from user side. | |
| 21 | 20 | // |
| 22 | // `wc` optional WC config | |
| 23 | func ETA(style int, age float64, startBlock chan time.Time, wc ...WC) Decorator { | |
| 24 | var wc0 WC | |
| 25 | if len(wc) > 0 { | |
| 26 | wc0 = wc[0] | |
| 21 | // `wcc` optional WC config | |
| 22 | func ETA(style int, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 23 | var wc WC | |
| 24 | for _, widthConf := range wcc { | |
| 25 | wc = widthConf | |
| 27 | 26 | } |
| 28 | wc0.BuildFormat() | |
| 27 | wc.BuildFormat() | |
| 29 | 28 | if age == .0 { |
| 30 | 29 | age = ewma.AVG_METRIC_AGE |
| 31 | 30 | } |
| 32 | eta := &ewmaETA{ | |
| 33 | mAverage: ewma.NewMovingAverage(age), | |
| 34 | startBlockReceiver: startBlock, | |
| 35 | startBlockStreamer: make(chan time.Time), | |
| 36 | style: style, | |
| 37 | wc: wc0, | |
| 31 | d := &ewmaETA{ | |
| 32 | style: style, | |
| 33 | wc: wc, | |
| 34 | mAverage: ewma.NewMovingAverage(age), | |
| 35 | sbReceiver: sbCh, | |
| 36 | sbStreamer: make(chan time.Time), | |
| 38 | 37 | } |
| 39 | go eta.serve() | |
| 40 | return eta | |
| 38 | go d.serve() | |
| 39 | return d | |
| 41 | 40 | } |
| 42 | 41 | |
| 43 | 42 | type ewmaETA struct { |
| 44 | mAverage ewma.MovingAverage | |
| 45 | startBlockReceiver chan time.Time | |
| 46 | startBlockStreamer chan time.Time | |
| 47 | style int | |
| 48 | wc WC | |
| 49 | onComplete *struct { | |
| 43 | style int | |
| 44 | wc WC | |
| 45 | mAverage ewma.MovingAverage | |
| 46 | sbReceiver chan time.Time | |
| 47 | sbStreamer chan time.Time | |
| 48 | onComplete *struct { | |
| 50 | 49 | msg string |
| 51 | 50 | wc WC |
| 52 | 51 | } |
| 78 | 77 | } |
| 79 | 78 | |
| 80 | 79 | func (s *ewmaETA) NextAmount(n int) { |
| 81 | sb := <-s.startBlockStreamer | |
| 80 | sb := <-s.sbStreamer | |
| 82 | 81 | lastBlockTime := time.Since(sb) |
| 83 | 82 | lastItemEstimate := float64(lastBlockTime) / float64(n) |
| 84 | 83 | s.mAverage.Add(lastItemEstimate) |
| 85 | 84 | } |
| 86 | 85 | |
| 87 | func (s *ewmaETA) OnCompleteMessage(msg string, wc ...WC) { | |
| 88 | var wc0 WC | |
| 89 | if len(wc) > 0 { | |
| 90 | wc0 = wc[0] | |
| 86 | func (s *ewmaETA) OnCompleteMessage(msg string, wcc ...WC) { | |
| 87 | var wc WC | |
| 88 | for _, widthConf := range wcc { | |
| 89 | wc = widthConf | |
| 91 | 90 | } |
| 92 | wc0.BuildFormat() | |
| 91 | wc.BuildFormat() | |
| 93 | 92 | s.onComplete = &struct { |
| 94 | 93 | msg string |
| 95 | 94 | wc WC |
| 96 | }{msg, wc0} | |
| 95 | }{msg, wc} | |
| 97 | 96 | } |
| 98 | 97 | |
| 99 | 98 | func (s *ewmaETA) Shutdown() { |
| 100 | close(s.startBlockReceiver) | |
| 99 | close(s.sbReceiver) | |
| 101 | 100 | } |
| 102 | 101 | |
| 103 | 102 | func (s *ewmaETA) serve() { |
| 104 | for now := range s.startBlockReceiver { | |
| 105 | s.startBlockStreamer <- now | |
| 103 | for now := range s.sbReceiver { | |
| 104 | s.sbStreamer <- now | |
| 106 | 105 | } |
| 107 | 106 | } |
| 0 | package decor | |
| 1 | ||
| 2 | // StaticName returns name decorator. | |
| 3 | // | |
| 4 | // `name` string to display | |
| 5 | // | |
| 6 | // `wcc` optional WC config | |
| 7 | func StaticName(name string, wcc ...WC) Decorator { | |
| 8 | return Name(name, wcc...) | |
| 9 | } | |
| 10 | ||
| 11 | // Name returns name decorator. | |
| 12 | // | |
| 13 | // `name` string to display | |
| 14 | // | |
| 15 | // `wcc` optional WC config | |
| 16 | func Name(name string, wcc ...WC) Decorator { | |
| 17 | var wc WC | |
| 18 | for _, widthConf := range wcc { | |
| 19 | wc = widthConf | |
| 20 | } | |
| 21 | wc.BuildFormat() | |
| 22 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 23 | return wc.FormatMsg(name, widthAccumulator, widthDistributor) | |
| 24 | }) | |
| 25 | } |
| 0 | package decor | |
| 1 | ||
| 2 | import "fmt" | |
| 3 | ||
| 4 | // Percentage returns percentage decorator. | |
| 5 | // | |
| 6 | // `wcc` optional WC config | |
| 7 | func Percentage(wcc ...WC) Decorator { | |
| 8 | var wc WC | |
| 9 | for _, widthConf := range wcc { | |
| 10 | wc = widthConf | |
| 11 | } | |
| 12 | wc.BuildFormat() | |
| 13 | return DecoratorFunc(func(s *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 14 | str := fmt.Sprintf("%d %%", CalcPercentage(s.Total, s.Current, 100)) | |
| 15 | return wc.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 16 | }) | |
| 17 | } | |
| 18 | ||
| 19 | // CalcPercentage is a helper function, to calculate percentage. | |
| 20 | func CalcPercentage(total, current, width int64) int64 { | |
| 21 | if total <= 0 { | |
| 22 | return 0 | |
| 23 | } | |
| 24 | p := float64(width*current) / float64(total) | |
| 25 | return int64(round(p)) | |
| 26 | } |
| 4 | 4 | "io" |
| 5 | 5 | "strconv" |
| 6 | 6 | "strings" |
| 7 | "time" | |
| 8 | ||
| 9 | "github.com/VividCortex/ewma" | |
| 7 | 10 | ) |
| 8 | 11 | |
| 9 | 12 | type SpeedKiB float64 |
| 113 | 116 | |
| 114 | 117 | io.WriteString(st, res) |
| 115 | 118 | } |
| 119 | ||
| 120 | // SpeedNoUnit returns raw I/O operation speed decorator. | |
| 121 | // | |
| 122 | // `unitFormat` printf compatible verb for value, like "%f" or "%d" | |
| 123 | // | |
| 124 | // `age` is the previous N samples to average over. | |
| 125 | // If zero value provided, it defaults to 30. | |
| 126 | // | |
| 127 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 128 | // to this channel on each iteration of a start block, right before actual job. | |
| 129 | // The channel will be auto closed on bar shutdown event, so there is no need | |
| 130 | // to close from user side. | |
| 131 | // | |
| 132 | // `wcc` optional WC config | |
| 133 | // | |
| 134 | // unitFormat example: | |
| 135 | // | |
| 136 | // "%.1f" = "1.0" or "% .1f" = "1.0" | |
| 137 | func SpeedNoUnit(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 138 | return speed(0, unitFormat, age, sbCh, wcc...) | |
| 139 | } | |
| 140 | ||
| 141 | // SpeedKibiByte returns human friendly I/O operation speed decorator, | |
| 142 | // | |
| 143 | // `unitFormat` printf compatible verb for value, like "%f" or "%d" | |
| 144 | // | |
| 145 | // `age` is the previous N samples to average over. | |
| 146 | // If zero value provided, it defaults to 30. | |
| 147 | // | |
| 148 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 149 | // to this channel on each iteration of a start block, right before actual job. | |
| 150 | // The channel will be auto closed on bar shutdown event, so there is no need | |
| 151 | // to close from user side. | |
| 152 | // | |
| 153 | // `wcc` optional WC config | |
| 154 | // | |
| 155 | // unitFormat example: | |
| 156 | // | |
| 157 | // "%.1f" = "1.0MiB/s" or "% .1f" = "1.0 MiB/s" | |
| 158 | func SpeedKibiByte(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 159 | return speed(unitKiB, unitFormat, age, sbCh, wcc...) | |
| 160 | } | |
| 161 | ||
| 162 | // SpeedKiloByte returns human friendly I/O operation speed decorator, | |
| 163 | // | |
| 164 | // `unitFormat` printf compatible verb for value, like "%f" or "%d" | |
| 165 | // | |
| 166 | // `age` is the previous N samples to average over. | |
| 167 | // If zero value provided, it defaults to 30. | |
| 168 | // | |
| 169 | // `sbCh` is a start block receive channel. User suppose to send time.Now() | |
| 170 | // to this channel on each iteration of a start block, right before actual job. | |
| 171 | // The channel will be auto closed on bar shutdown event, so there is no need | |
| 172 | // to close from user side. | |
| 173 | // | |
| 174 | // `wcc` optional WC config | |
| 175 | // | |
| 176 | // unitFormat example: | |
| 177 | // | |
| 178 | // "%.1f" = "1.0MB/s" or "% .1f" = "1.0 MB/s" | |
| 179 | func SpeedKiloByte(unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 180 | return speed(unitKB, unitFormat, age, sbCh, wcc...) | |
| 181 | } | |
| 182 | ||
| 183 | func speed(unit int, unitFormat string, age float64, sbCh chan time.Time, wcc ...WC) Decorator { | |
| 184 | var wc WC | |
| 185 | for _, widthConf := range wcc { | |
| 186 | wc = widthConf | |
| 187 | } | |
| 188 | wc.BuildFormat() | |
| 189 | if age == .0 { | |
| 190 | age = ewma.AVG_METRIC_AGE | |
| 191 | } | |
| 192 | d := &ewmaSpeed{ | |
| 193 | unit: unit, | |
| 194 | unitFormat: unitFormat, | |
| 195 | wc: wc, | |
| 196 | mAverage: ewma.NewMovingAverage(age), | |
| 197 | sbReceiver: sbCh, | |
| 198 | sbStreamer: make(chan time.Time), | |
| 199 | } | |
| 200 | go d.serve() | |
| 201 | return d | |
| 202 | } | |
| 203 | ||
| 204 | type ewmaSpeed struct { | |
| 205 | unit int | |
| 206 | unitFormat string | |
| 207 | wc WC | |
| 208 | mAverage ewma.MovingAverage | |
| 209 | sbReceiver chan time.Time | |
| 210 | sbStreamer chan time.Time | |
| 211 | onComplete *struct { | |
| 212 | msg string | |
| 213 | wc WC | |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | func (s *ewmaSpeed) Decor(st *Statistics, widthAccumulator chan<- int, widthDistributor <-chan int) string { | |
| 218 | if st.Completed && s.onComplete != nil { | |
| 219 | return s.onComplete.wc.FormatMsg(s.onComplete.msg, widthAccumulator, widthDistributor) | |
| 220 | } | |
| 221 | var str string | |
| 222 | speed := round(s.mAverage.Value()) | |
| 223 | switch s.unit { | |
| 224 | case unitKiB: | |
| 225 | str = fmt.Sprintf(s.unitFormat, SpeedKiB(speed)) | |
| 226 | case unitKB: | |
| 227 | str = fmt.Sprintf(s.unitFormat, SpeedKB(speed)) | |
| 228 | default: | |
| 229 | str = fmt.Sprintf(s.unitFormat, speed) | |
| 230 | } | |
| 231 | return s.wc.FormatMsg(str, widthAccumulator, widthDistributor) | |
| 232 | } | |
| 233 | ||
| 234 | func (s *ewmaSpeed) NextAmount(n int) { | |
| 235 | sb := <-s.sbStreamer | |
| 236 | speed := float64(n) / time.Since(sb).Seconds() | |
| 237 | s.mAverage.Add(speed) | |
| 238 | } | |
| 239 | ||
| 240 | func (s *ewmaSpeed) OnCompleteMessage(msg string, wcc ...WC) { | |
| 241 | var wc WC | |
| 242 | for _, widthConf := range wcc { | |
| 243 | wc = widthConf | |
| 244 | } | |
| 245 | wc.BuildFormat() | |
| 246 | s.onComplete = &struct { | |
| 247 | msg string | |
| 248 | wc WC | |
| 249 | }{msg, wc} | |
| 250 | } | |
| 251 | ||
| 252 | func (s *ewmaSpeed) Shutdown() { | |
| 253 | close(s.sbReceiver) | |
| 254 | } | |
| 255 | ||
| 256 | func (s *ewmaSpeed) serve() { | |
| 257 | for now := range s.sbReceiver { | |
| 258 | s.sbStreamer <- now | |
| 259 | } | |
| 260 | } | |
| 57 | 57 | return |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | startBlock := make(chan time.Time) | |
| 61 | ||
| 60 | sbEta := make(chan time.Time) | |
| 61 | sbSpeed := make(chan time.Time) | |
| 62 | 62 | // create bar with appropriate decorators |
| 63 | 63 | bar := p.AddBar(size, mpb.BarPriority(n), |
| 64 | 64 | mpb.PrependDecorators( |
| 66 | 66 | decor.CountersKibiByte("%6.1f / %6.1f", decor.WCSyncWidth), |
| 67 | 67 | ), |
| 68 | 68 | mpb.AppendDecorators( |
| 69 | decor.ETA(decor.ET_STYLE_HHMMSS, 60, startBlock, decor.WCSyncWidth), | |
| 70 | decor.SpeedKibiByte("%6.1f", decor.WCSyncSpace), | |
| 69 | decor.ETA(decor.ET_STYLE_HHMMSS, 60, sbEta, decor.WCSyncWidth), | |
| 70 | decor.SpeedKibiByte("% .2f", 60, sbSpeed, decor.WCSyncSpace), | |
| 71 | 71 | ), |
| 72 | 72 | ) |
| 73 | 73 | |
| 74 | 74 | // create proxy reader |
| 75 | reader := bar.ProxyReader(resp.Body, startBlock) | |
| 75 | reader := bar.ProxyReader(resp.Body, sbEta, sbSpeed) | |
| 76 | 76 | // and copy from reader |
| 77 | 77 | _, err = io.Copy(dest, reader) |
| 78 | 78 | |
| 38 | 38 | |
| 39 | 39 | p := mpb.New(mpb.WithWidth(64)) |
| 40 | 40 | |
| 41 | startBlock := make(chan time.Time) | |
| 41 | sbEta := make(chan time.Time) | |
| 42 | sbSpeed := make(chan time.Time) | |
| 42 | 43 | bar := p.AddBar(size, |
| 43 | 44 | mpb.PrependDecorators( |
| 44 | 45 | decor.CountersKibiByte("% 6.1f / % 6.1f", decor.WC{W: 18}), |
| 45 | 46 | ), |
| 46 | 47 | mpb.AppendDecorators( |
| 47 | decor.ETA(decor.ET_STYLE_HHMMSS, 120, startBlock), | |
| 48 | decor.SpeedKibiByte("% 6.1f", decor.WC{W: 14}), | |
| 48 | decor.ETA(decor.ET_STYLE_HHMMSS, 120, sbEta), | |
| 49 | decor.SpeedKibiByte("% .2f", 120, sbSpeed, decor.WC{W: 14}), | |
| 49 | 50 | ), |
| 50 | 51 | ) |
| 51 | 52 | |
| 52 | 53 | // create proxy reader |
| 53 | reader := bar.ProxyReader(resp.Body, startBlock) | |
| 54 | reader := bar.ProxyReader(resp.Body, sbEta, sbSpeed) | |
| 54 | 55 | |
| 55 | 56 | // and copy from reader, ignoring errors |
| 56 | 57 | io.Copy(dest, reader) |
| 7 | 7 | // Reader is io.Reader wrapper, for proxy read bytes |
| 8 | 8 | type Reader struct { |
| 9 | 9 | io.Reader |
| 10 | bar *Bar | |
| 11 | startBlockCh chan<- time.Time | |
| 10 | bar *Bar | |
| 11 | sbChannels []chan<- time.Time | |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | 14 | func (r *Reader) Read(p []byte) (int, error) { |
| 15 | if r.startBlockCh != nil { | |
| 16 | r.startBlockCh <- time.Now() | |
| 15 | now := time.Now() | |
| 16 | for _, ch := range r.sbChannels { | |
| 17 | ch <- now | |
| 17 | 18 | } |
| 18 | 19 | n, err := r.Reader.Read(p) |
| 19 | 20 | r.bar.IncrBy(n) |