package main import ( "fmt" "sort" "sync" "time" ) // JobWaitGroup is like sync.WaitGroup, but keeps track of job status, // and tracks how long each job took. type JobWaitGroup struct { lock sync.RWMutex jobs map[string]time.Duration wg sync.WaitGroup } // Do a job. 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() }() } // Wait for all jobs to finish, and return how long each job took. func (jwg *JobWaitGroup) Wait() map[string]time.Duration { jwg.wg.Wait() return jwg.jobs } // Status returns the total number of jobs that have been started, and // a list of still-running jobs. 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 }