github.com/fluhus/gostuff@v0.4.1-0.20240331134726-be71864f2b5d/ptimer/ptimer.go (about) 1 // Package ptimer provides a progress timer for iterative processes. 2 // 3 // A timer prints how much time passed since its creation at exponentially 4 // growing time-points. 5 // Precisely, prints are triggered after i calls to Inc, if i has only one non-zero 6 // digit. That is: 1, 2, 3 .. 9, 10, 20, 30 .. 90, 100, 200, 300... 7 // 8 // # Output Format 9 // 10 // For a regular use: 11 // 12 // 00:00:00.000000 (00:00:00.000000) message 13 // | | | 14 // Total time since creation | | 15 // | | 16 // Average time per call to Inc | 17 // | 18 // User-defined message ----------------| 19 // (default message is number of calls to Inc) 20 // 21 // When calling Done without calling Inc: 22 // 23 // 00:00:00.000000 message 24 package ptimer 25 26 import ( 27 "fmt" 28 "io" 29 "os" 30 "strings" 31 "time" 32 ) 33 34 // A Timer measures time during iterative processes and prints the progress on 35 // exponential checkpoints. 36 type Timer struct { 37 N int // Current count, incremented with each call to Inc 38 W io.Writer // Timer's output, defaults to stderr 39 t time.Time // Start time 40 f func(int) string // Message function 41 c int // Next checkpoint 42 } 43 44 // Prints the progress. 45 func (t *Timer) print() { 46 since := time.Since(t.t) 47 if t.N == 0 { // Happens when calling Done without Inc. 48 fmt.Fprintf(t.W, "%s %s", fmtDuration(since), t.f(t.N)) 49 return 50 } 51 fmt.Fprintf(t.W, "\r%s (%s) %s", fmtDuration(since), 52 fmtDuration(since/time.Duration(t.N)), t.f(t.N)) 53 } 54 55 // Formats a duration in constant-width format. 56 func fmtDuration(d time.Duration) string { 57 return fmt.Sprintf("%02d:%02d:%02d.%06d", 58 d/time.Hour, 59 d%time.Hour/time.Minute, 60 d%time.Minute/time.Second, 61 d%time.Second/time.Microsecond, 62 ) 63 } 64 65 // NewFunc returns a new timer that calls f with the current count on checkpoints, 66 // and prints its output. 67 func NewFunc(f func(i int) string) *Timer { 68 return &Timer{0, os.Stderr, time.Now(), f, 0} 69 } 70 71 // NewMessage returns a new timer that prints msg on checkpoints. 72 // A "{}" in msg will be replaced with the current count. 73 func NewMessage(msg string) *Timer { 74 return NewFunc(func(i int) string { 75 return strings.ReplaceAll(msg, "{}", fmt.Sprint(i)) 76 }) 77 } 78 79 // New returns a new timer that prints the current count on checkpoints. 80 func New() *Timer { 81 return NewMessage("{}") 82 } 83 84 // Inc increments t's counter and prints progress if reached a checkpoint. 85 func (t *Timer) Inc() { 86 t.N++ 87 if t.N >= t.c { 88 t.print() 89 for t.c <= t.N { 90 t.c = nextCheckpoint(t.c) 91 } 92 } 93 } 94 95 // Done prints progress as if a checkpoint was reached. 96 func (t *Timer) Done() { 97 t.print() 98 fmt.Fprintln(t.W) 99 } 100 101 // Returns the next i in which the timer should print, given the current i. 102 func nextCheckpoint(i int) int { 103 m := 1 104 for m*10 <= i { 105 m *= 10 106 } 107 if i%m != 0 { 108 panic(fmt.Sprintf( 109 "bad checkpoint: %d, should be a multiple of a power of 10", i)) 110 } 111 return i + m 112 }