cmd/timerdisplay.go
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"io"
"os"
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/gosuri/uilive"
)
// TimerDisplay displays timing for incremental updates, like plan and apply
type TimerDisplay struct {
writer *uilive.Writer
once sync.Once
started bool
logger *logrus.Entry
items []*timerDisplayTimer
wlock *sync.RWMutex
}
func (td *TimerDisplay) init() {
td.writer = uilive.New()
td.writer.RefreshInterval = 250 * time.Millisecond
td.writer.Out = os.Stderr
td.logger = logrus.WithField("component", "timer display")
td.logger.Logger.Out = td.writer.Bypass()
td.wlock = new(sync.RWMutex)
}
// Message creates the current message to display
func (td *TimerDisplay) Message() string {
td.once.Do(td.init)
out := "\nActive:\n"
td.wlock.RLock()
defer td.wlock.RUnlock()
for _, item := range td.items {
out += item.String() + "\n"
}
return out
}
// AddTimer starts a timer display for the given name
func (td *TimerDisplay) AddTimer(name string) {
td.once.Do(td.init)
td.logger.WithField("name", name).Debug("adding timer")
td.wlock.Lock()
defer td.wlock.Unlock()
td.items = append(td.items, &timerDisplayTimer{name, time.Now()})
}
// RemoveTimer removes a timer display for the given name
func (td *TimerDisplay) RemoveTimer(name string) {
td.once.Do(td.init)
td.logger.WithField("name", name).Debug("removing timer")
td.wlock.Lock()
defer td.wlock.Unlock()
for i, item := range td.items {
if item.Name == name {
td.items = append(td.items[:i], td.items[i+1:]...)
}
}
}
// Bypass provides an io.Writer that can write without interfering with this display
func (td *TimerDisplay) Bypass() io.Writer {
td.once.Do(td.init)
return td.writer.Bypass()
}
// Start starts the display
func (td *TimerDisplay) Start() {
if !CanUseEscapeSequences() {
return
}
td.once.Do(td.init)
td.writer.Start()
td.started = true
go func() {
for td.started {
fmt.Fprint(td.writer, td.Message())
time.Sleep(td.writer.RefreshInterval)
}
}()
}
// Stop stops the display
func (td *TimerDisplay) Stop() {
if !CanUseEscapeSequences() {
return
}
td.once.Do(td.init)
td.started = false
td.writer.Flush()
fmt.Fprint(td.writer, " ")
td.writer.Flush()
td.writer.Stop()
}
type timerDisplayTimer struct {
Name string
Start time.Time
}
func (t *timerDisplayTimer) String() string {
elapsed := time.Since(t.Start)
elapsedRounded := elapsed - (elapsed % time.Second)
return fmt.Sprintf("%s (running %s)", t.Name, elapsedRounded)
}