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  }