asteris-llc/converge

View on GitHub
cmd/timerdisplay.go

Summary

Maintainability
A
0 mins
Test Coverage
// 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)
}