summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@beefcake.parabola.nu>2018-05-19 12:25:39 -0400
committerLuke Shumaker <lukeshu@beefcake.parabola.nu>2018-05-19 12:25:39 -0400
commit9110ae474a8ac9fd70b6e15aa3cfda368c5d1cb4 (patch)
tree2242712710f65b67cad2b527f5db98e31fc0c095
parent19eeecd2821a36fe647584026379645a8764735b (diff)
statusline fanciness
-rw-r--r--go/src/cow-dedupe/dedupe.go28
-rw-r--r--go/src/lib/statusline/async.go57
-rw-r--r--go/src/lib/statusline/ratelimit.go58
-rw-r--r--go/src/lib/statusline/statuslinue.go21
-rw-r--r--go/src/lib/statusline/stopwatch.go71
5 files changed, 161 insertions, 74 deletions
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
+}