package main import ( "fmt" "os" "sort" "strings" "sync" "time" ) type JobWaitGroup struct { lock sync.RWMutex jobs map[string]time.Duration wg sync.WaitGroup } func (jwg *JobWaitGroup) Do(name string, fn func()) { jwg.lock.Lock() defer jwg.lock.Unlock() if jwg.jobs == nil { jwg.jobs = make(map[string]time.Duration) } if _, dup := jwg.jobs[name]; dup { panic(fmt.Sprintf("job %q already exists", name)) } jwg.jobs[name] = -1 jwg.wg.Add(1) start := time.Now() go func() { defer func() { jwg.lock.Lock() defer jwg.lock.Unlock() jwg.jobs[name] = time.Now().Sub(start) jwg.wg.Done() }() fn() }() } func (jwg *JobWaitGroup) Wait() { jwg.wg.Wait() } func (jwg *JobWaitGroup) Status() (int, []string) { jwg.lock.RLock() n := len(jwg.jobs) var jobs []string for job, duration := range jwg.jobs { if duration < 0 { jobs = append(jobs, job) } } defer jwg.lock.RUnlock() sort.Strings(jobs) return n, jobs } func (jwg *JobWaitGroup) Watch(d time.Duration) { ticker := time.NewTicker(d) done := make(chan struct{}) go func() { jwg.Wait() ticker.Stop() done <- struct{}{} }() for { select { case <-ticker.C: n, jobs := jwg.Status() if len(jobs) == 0 { panic("no active jobs, but wg still waiting") } line := fmt.Sprintf("%d/%d : %v", len(jobs), n, strings.Join(jobs, ", ")) if len(line) > 70 { line = line[:67] + "..." } fmt.Fprintf(os.Stderr, "\r%-70s", line) case <-done: fmt.Fprintf(os.Stderr, "\r%-70s\n", "done") jwg.lock.RLock() defer jwg.lock.RUnlock() s := newSortHelper(jwg.jobs) sort.Sort(s) for _, job := range s.StringSlice { fmt.Fprintln(os.Stderr, s.times[job], job) } return } } } type sortHelper struct { times map[string]time.Duration sort.StringSlice } func (s sortHelper) Less(i, j int) bool { return s.times[s.StringSlice[i]] < s.times[s.StringSlice[j]] } func newSortHelper(jobs map[string]time.Duration) sortHelper { slice := make([]string, 0, len(jobs)) for job := range jobs { slice = append(slice, job) } return sortHelper{times: jobs, StringSlice: slice} }