---
title: "Progress and time remaining"
pubDate: 2022-12-05
author: "rsdoiel@sdf.org (R. S. Doiel)"
keywords: [ "programming", "golang", "log info" ]
---

# Progress and time remaining

By R. S. Doiel, 2022-11-05

I often find myself logging output when I'm developing tools.  This is typically the case where I am iterating over data and transforming it. Overtime I've come to realize I really want a few specific pieces of information for non-error logging (e.g. `-verbose` which monitors progress as well as errors).

- percentage completed
- estimated time allocated (i.e. time remaining)

To do that I need three pieces of information.

1. the count of the current iteration(e.g. `i`)
2. the total number of iterations required (e.g. `tot`)
3. The time just before I started iterating(e.g. `t0`)

The values for `i` and `tot` let me compute the percent completed. The percent completed is trivial `(i/tot) * 100.0`. Note on the first pass (i.e. `i == 0`) you can skip the percentage calculation.


```golang
import (
	"time"
	"fmt"
)

// Show progress with amount of time running
func progress(t0 time.Time, i int, tot int) string {
    if i == 0 {
        return ""
    }
	percent := (float64(i) / float64(tot)) * 100.0
	t1 := time.Now()
	// NOTE: Truncating the duration to seconds
	return fmt.Sprintf("%.2f%% %v", percent, t1.Sub(t0).Truncate(time.Second))
}
```

Here's how you might use it.

```golang
	tot := len(ids)
	t0 := time.Now()
	for i, id := range ids {
		// ... processing stuff here ... and display progress every 1000 records
		if (i % 1000) == 0 {
			log.Printf("%s records processed", progress(t0, i, tot))
		}
	}
```

An improvement on this is to include an time remaining. I need to calculated the estimated time allocation (i.e. ETA). I know `t0` so I can estimate that with this formula `estimated time allocation = (((current running time since t0)/ the number of items processed) * total number of items)`[^1]. ETA adjusted for time running gives us time remaining[^2]. The first pass of the function progress has a trivial optimization since we don't have enough delta t0 to compute an estimate. Calls after that are computed using our formula.

[^1]: In code `(rt/i)*tot` is estimated time allocation

[^2]: Estimated Time Remaining, in code `((rt/i)*tot) - rt`

```golang
func progress(t0 time.Time, i int, tot int) string {
	if i == 0 {
		return "0.00 ETR Unknown"
	}
	// percent completed
	percent := (float64(i) / float64(tot)) * 100.0
	// running time
    rt := time.Now().Sub(t0)
    // estimated time allocation - running time = time remaining
    eta := time.Duration((float64(rt)/float64(i)*float64(tot)) - float64(rt))
    return fmt.Sprintf("%.2f%% ETR %v", percent, eta.Truncate(time.Second))
}
```