From 9110ae474a8ac9fd70b6e15aa3cfda368c5d1cb4 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 19 May 2018 12:25:39 -0400 Subject: statusline fanciness --- go/src/cow-dedupe/dedupe.go | 28 ++++++++------ go/src/lib/statusline/async.go | 57 ----------------------------- go/src/lib/statusline/ratelimit.go | 58 +++++++++++++++++++++++++++++ go/src/lib/statusline/statuslinue.go | 21 ++++++++--- go/src/lib/statusline/stopwatch.go | 71 ++++++++++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 74 deletions(-) delete mode 100644 go/src/lib/statusline/async.go create mode 100644 go/src/lib/statusline/ratelimit.go create mode 100644 go/src/lib/statusline/stopwatch.go diff --git a/go/src/cow-dedupe/dedupe.go b/go/src/cow-dedupe/dedupe.go index ea60604..a47e31f 100644 --- a/go/src/cow-dedupe/dedupe.go +++ b/go/src/cow-dedupe/dedupe.go @@ -26,6 +26,10 @@ func errhandle(err error) { } } +func myStatusLine() statusline.StatusLine { + return statusline.StopWatch(statusline.RateLimit(statusline.New(os.Stderr), time.Second/2), time.Second) +} + func getFiemaps(paths []string) map[string][]string { var err error for i := range paths { @@ -35,7 +39,7 @@ func getFiemaps(paths []string) map[string][]string { ret := map[string][]string{} - sl := statusline.NewAsyncStatusLine(os.Stderr, time.Second/2) + sl := myStatusLine() cnt := 0 sl.Put("Mapping extents...") @@ -71,15 +75,15 @@ func getFiemaps(paths []string) map[string][]string { } errhandle(cmd.Wait()) - sl.End() - fmt.Fprintf(os.Stderr, "Mapping extents... done; mapped %d files\n", cnt) + sl.Put(fmt.Sprintf("Mapping extents... done; mapped %d files", cnt)) + sl.End(true) return ret } func getChecksums(paths []string) map[string][]string { ret := map[string][]string{} - sl := statusline.NewAsyncStatusLine(os.Stderr, time.Second/2) + sl := myStatusLine() cnt := 0 sl.Put(fmt.Sprintf("Generating checksums for files... %d/%d", cnt, len(paths))) @@ -121,8 +125,8 @@ func getChecksums(paths []string) map[string][]string { errhandle(cmd.Wait()) } - sl.End() - fmt.Fprintf(os.Stderr, "Generating checksums for files... done; summed %d files\n", cnt) + sl.Put(fmt.Sprintf("Generating checksums for files... done; summed %d files", cnt)) + sl.End(true) return ret } @@ -133,7 +137,8 @@ func main() { fiemap2filenames := getFiemaps(os.Args[1:]) - fmt.Fprintf(os.Stderr, "Building list of spanning files...") + sl := statusline.StopWatch(statusline.New(os.Stderr), time.Second) + sl.Put( "Building list of spanning files...") filename2fiemap := map[string]string{} for fiemap, filenames := range fiemap2filenames { @@ -152,7 +157,8 @@ func main() { i++ } - fmt.Fprintf(os.Stderr, " done (%d files)\n", len(spanningFiles)) + sl.Put(fmt.Sprintf("Building list of spanning files... done; %d files", len(spanningFiles))) + sl.End(true) checksum2filenames := getChecksums(spanningFiles) @@ -168,7 +174,7 @@ func main() { } } - sl := statusline.NewAsyncStatusLine(os.Stderr, time.Second/2) + sl = myStatusLine() cnt := 0 sl.Put(fmt.Sprintf("Deduplicating sets of files... %d/%d", cnt, len(checksum2fiemaps))) for checksum, fiemaps := range checksum2fiemaps { @@ -211,6 +217,6 @@ func main() { cnt++ sl.Put(fmt.Sprintf("Deduplicating sets of files... %d/%d", cnt, len(checksum2fiemaps))) } - sl.End() - fmt.Fprintf(os.Stderr, "Deduplicating sets of files... done; deduplicated %d sets", cnt) + sl.Put(fmt.Sprintf("Deduplicating sets of files... done; deduplicated %d sets", cnt)) + sl.End(true) } diff --git a/go/src/lib/statusline/async.go b/go/src/lib/statusline/async.go deleted file mode 100644 index 5b509ac..0000000 --- a/go/src/lib/statusline/async.go +++ /dev/null @@ -1,57 +0,0 @@ -package statusline - -import ( - "io" - "time" -) - -type AsyncStatusLine struct { - lines chan string - end1 chan struct{} - end2 chan struct{} -} - -func NewAsyncStatusLine(out io.Writer, d time.Duration) *AsyncStatusLine { - ret := &AsyncStatusLine{ - lines: make(chan string), - end1: make(chan struct{}), - end2: make(chan struct{}), - } - go func() { - sl := NewStatusLine(out) - ticker := time.NewTicker(d) - var oldLine string - var newLine string - dirty := false - for { - select { - case <-ticker.C: - if dirty && newLine != oldLine { - sl.Put(newLine) - dirty = false - } - case l := <-ret.lines: - newLine = l - dirty = true - case <-ret.end1: - sl.End() - ticker.Stop() - end2 <- struct{}{} - close(end2) - return - } - } - }() - return ret -} - -func (sl *AsyncStatusLine) Put(line string) { - sl.lines <- line -} - -func (sl *AsyncStatusLine) End() { - sl.end1 <- struct{}{} - close(sl.lines) - close(sl.end) - <-sl.end2 -} diff --git a/go/src/lib/statusline/ratelimit.go b/go/src/lib/statusline/ratelimit.go new file mode 100644 index 0000000..970f8e5 --- /dev/null +++ b/go/src/lib/statusline/ratelimit.go @@ -0,0 +1,58 @@ +package statusline + +import ( + "time" +) + +type rateLimiter struct { + lines chan string + end1 chan bool + end2 chan struct{} +} + +func RateLimit(sl StatusLine, d time.Duration) StatusLine { + ret := &rateLimiter{ + lines: make(chan string), + end1: make(chan bool), + end2: make(chan struct{}), + } + go func() { + ticker := time.NewTicker(d) + var oldLine string + var newLine string + dirty := false + for { + select { + case <-ticker.C: + if dirty && newLine != oldLine { + sl.Put(newLine) + } + dirty = false + case line := <-ret.lines: + newLine = line + dirty = true + case keep := <-ret.end1: + if keep && dirty && newLine != oldLine { + sl.Put(newLine) + } + sl.End(keep) + ticker.Stop() + ret.end2 <- struct{}{} + close(ret.end2) + return + } + } + }() + return ret +} + +func (sl *rateLimiter) Put(line string) { + sl.lines <- line +} + +func (sl *rateLimiter) End(keep bool) { + sl.end1 <- keep + close(sl.lines) + close(sl.end1) + <-sl.end2 +} diff --git a/go/src/lib/statusline/statuslinue.go b/go/src/lib/statusline/statuslinue.go index 90ec1af..323c178 100644 --- a/go/src/lib/statusline/statuslinue.go +++ b/go/src/lib/statusline/statuslinue.go @@ -5,20 +5,29 @@ import ( "io" ) -type StatusLine struct { +type StatusLine interface { + Put(line string) + End(keep bool) +} + +type statusLine struct { out io.Writer prevLen int } -func NewStatusLine(out io.Writer) *StatusLine { - return &StatusLine{out: out} +func New(out io.Writer) StatusLine { + return &statusLine{out: out} } -func (sl *StatusLine) Put(line string) { +func (sl *statusLine) Put(line string) { fmt.Fprintf(sl.out, "\r%-[1]*[2]s", sl.prevLen, line) sl.prevLen = len(line) } -func (sl *StatusLine) End() { - fmt.Fprintf(sl.out, "\r%-[1]*[2]s\r", sl.prevLen, "") +func (sl *statusLine) End(keep bool) { + if (keep) { + io.WriteString(sl.out, "\n") + } else { + fmt.Fprintf(sl.out, "\r%-[1]*[2]s\r", sl.prevLen, "") + } } diff --git a/go/src/lib/statusline/stopwatch.go b/go/src/lib/statusline/stopwatch.go new file mode 100644 index 0000000..4e3bb5b --- /dev/null +++ b/go/src/lib/statusline/stopwatch.go @@ -0,0 +1,71 @@ +package statusline + +import ( + "fmt" + "sync" + "time" +) + +type stopWatch struct { + inner StatusLine + precision time.Duration + + start time.Time + line string + + once sync.Once + + lines chan string + end1 chan bool + end2 chan struct{} +} + +func StopWatch(sl StatusLine, precision time.Duration) StatusLine { + return &stopWatch{ + inner: sl, + precision: precision, + lines: make(chan string), + end1: make(chan bool), + end2: make(chan struct{}), + } +} + +func (sw *stopWatch) startWorker() { + go func() { + sw.start = time.Now() + ticker := time.NewTicker(sw.precision) + for { + select { + case <-ticker.C: + sw.tick() + case sw.line = <-sw.lines: + sw.tick() + case keep := <-sw.end1: + sw.tick() + sw.inner.End(keep) + ticker.Stop() + sw.end2 <- struct{}{} + close(sw.end2) + return + } + } + }() +} + +func (sw *stopWatch) tick() { + d := time.Now().Sub(sw.start).Round(sw.precision) + sw.inner.Put(fmt.Sprintf("[ %v ] %s", d, sw.line)) +} + +func (sl *stopWatch) Put(line string) { + sl.once.Do(sl.startWorker) + sl.lines <- line +} + +func (sl *stopWatch) End(keep bool) { + sl.once.Do(sl.startWorker) + sl.end1 <- keep + close(sl.lines) + close(sl.end1) + <-sl.end2 +} -- cgit v1.2.3