Skip to content

Commit 7873b2e

Browse files
committed
Initial commit
1 parent fac187f commit 7873b2e

File tree

9 files changed

+311
-48
lines changed

9 files changed

+311
-48
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2021 bool64
3+
Copyright (c) 2023 Viacheslav Poturaev
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Makefile

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ ifeq ($(DEVGO_PATH),)
2727
endif
2828
endif
2929

30+
BUILD_PKG = ./cmd/catp
31+
3032
-include $(DEVGO_PATH)/makefiles/main.mk
3133
-include $(DEVGO_PATH)/makefiles/lint.mk
32-
-include $(DEVGO_PATH)/makefiles/test-unit.mk
33-
-include $(DEVGO_PATH)/makefiles/bench.mk
34+
-include $(DEVGO_PATH)/makefiles/build.mk
3435
-include $(DEVGO_PATH)/makefiles/reset-ci.mk
3536

3637
# Add your custom targets here.

README.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# go-template
1+
# progress
22

3-
[![Build Status](https://github.com/bool64/go-template/workflows/test-unit/badge.svg)](https://github.com/bool64/go-template/actions?query=branch%3Amaster+workflow%3Atest-unit)
4-
[![Coverage Status](https://codecov.io/gh/bool64/go-template/branch/master/graph/badge.svg)](https://codecov.io/gh/bool64/go-template)
5-
[![GoDevDoc](https://img.shields.io/badge/dev-doc-00ADD8?logo=go)](https://pkg.go.dev/github.com/bool64/go-template)
6-
[![Time Tracker](https://wakatime.com/badge/github/bool64/go-template.svg)](https://wakatime.com/badge/github/bool64/go-template)
7-
![Code lines](https://sloc.xyz/github/bool64/go-template/?category=code)
8-
![Comments](https://sloc.xyz/github/bool64/go-template/?category=comments)
3+
[![Build Status](https://github.com/bool64/progress/workflows/test-unit/badge.svg)](https://github.com/bool64/progress/actions?query=branch%3Amaster+workflow%3Atest-unit)
4+
[![Coverage Status](https://codecov.io/gh/bool64/progress/branch/master/graph/badge.svg)](https://codecov.io/gh/bool64/progress)
5+
[![GoDevDoc](https://img.shields.io/badge/dev-doc-00ADD8?logo=go)](https://pkg.go.dev/github.com/bool64/progress)
6+
[![Time Tracker](https://wakatime.com/badge/github/bool64/progress.svg)](https://wakatime.com/badge/github/bool64/progress)
7+
![Code lines](https://sloc.xyz/github/bool64/progress/?category=code)
8+
![Comments](https://sloc.xyz/github/bool64/progress/?category=comments)
99

1010
<!--- TODO Update README.md -->
1111

cmd/catp/main.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"log"
9+
"os"
10+
"time"
11+
12+
"github.com/bool64/progress"
13+
)
14+
15+
// Status renders ProgressStatus as a string.
16+
func Status(s progress.ProgressStatus) string {
17+
if s.Task != "" {
18+
s.Task += ": "
19+
}
20+
21+
res := fmt.Sprintf(s.Task+"%.1f%% bytes read, %.1f MB/s, elapsed %s, remaining %s",
22+
s.DonePercent, s.SpeedMBPS,
23+
s.Elapsed.Round(10*time.Millisecond).String(), s.Remaining.String())
24+
25+
return res
26+
}
27+
28+
func readFile(r io.Reader) {
29+
rd := bufio.NewReader(r)
30+
31+
_, err := io.Copy(os.Stdout, rd)
32+
if err != nil {
33+
log.Fatal(err)
34+
}
35+
}
36+
37+
var pr = &progress.Progress{
38+
Interval: 5 * time.Second,
39+
Print: func(status progress.ProgressStatus) {
40+
println(Status(status))
41+
},
42+
}
43+
44+
func cat(filename string) {
45+
file, err := os.Open(filename)
46+
if err != nil {
47+
log.Fatal(err)
48+
}
49+
defer file.Close()
50+
51+
st, err := file.Stat()
52+
if err != nil {
53+
log.Fatalf("failed to read file stats %s: %s", filename, err)
54+
}
55+
56+
cr := &progress.CountingReader{Reader: file}
57+
58+
pr.Start(st.Size(), func() int64 {
59+
return cr.Bytes()
60+
}, filename)
61+
defer pr.Stop()
62+
63+
readFile(cr)
64+
}
65+
66+
func main() {
67+
flag.Parse()
68+
for i := 0; i < flag.NArg(); i++ {
69+
cat(flag.Arg(i))
70+
}
71+
}

dev_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
package mypackage_test
1+
package progress_test
22

33
import _ "github.com/bool64/dev" // Include CI/Dev scripts to project.

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
module github.com/bool64/go-template
1+
module github.com/bool64/progress
22

33
go 1.17
44

5-
require github.com/bool64/dev v0.2.5
5+
require github.com/bool64/dev v0.2.25

go.sum

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,2 @@
1-
github.com/bool64/dev v0.2.5 h1:H0bylghwcjDBBhEwSFTjArEO9Dr8cCaB54QSOF7esOA=
2-
github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
3-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4-
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6-
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
7-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
8-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1+
github.com/bool64/dev v0.2.25 h1:p6euAfe1zLXb1qzLssm0lJnM5KhfUZp/Qjb2dsPkIKU=
2+
github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=

progress.go

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package progress
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"runtime"
7+
"sync/atomic"
8+
"time"
9+
)
10+
11+
// ProgressStatus describes current progress.
12+
type ProgressStatus struct {
13+
Task string
14+
DonePercent float64
15+
LinesCompleted int64
16+
SpeedMBPS float64
17+
SpeedLPS float64
18+
Elapsed time.Duration
19+
Remaining time.Duration
20+
Metrics []ProgressMetric
21+
}
22+
23+
// Progress reports reading performance.
24+
type Progress struct {
25+
Interval time.Duration
26+
Print func(status ProgressStatus)
27+
ShowHeapStats bool
28+
ShowLinesStats bool
29+
done chan bool
30+
lines int64
31+
task string
32+
current func() int64
33+
prnt func(s ProgressStatus)
34+
start time.Time
35+
tot float64
36+
metrics []ProgressMetric
37+
}
38+
39+
// ProgressType describes metric value.
40+
type ProgressType string
41+
42+
// ProgressType values.
43+
const (
44+
ProgressBytes = ProgressType("bytes")
45+
ProgressDuration = ProgressType("duration")
46+
ProgressGauge = ProgressType("gauge")
47+
)
48+
49+
// ProgressMetric is an operation metric.
50+
type ProgressMetric struct {
51+
Name string
52+
Type ProgressType
53+
Value *int64
54+
}
55+
56+
// DefaultStatus renders ProgressStatus as a string.
57+
func DefaultStatus(s ProgressStatus) string {
58+
if s.Task != "" {
59+
s.Task += ": "
60+
}
61+
62+
ms := runtime.MemStats{}
63+
runtime.ReadMemStats(&ms)
64+
65+
heapMB := ms.HeapInuse / (1024 * 1024)
66+
67+
res := fmt.Sprintf(s.Task+"%.1f%% bytes read, %d lines processed, %.1f l/s, %.1f MB/s, elapsed %s, remaining %s, heap %d MB",
68+
s.DonePercent, s.LinesCompleted, s.SpeedLPS, s.SpeedMBPS,
69+
s.Elapsed.Round(10*time.Millisecond).String(), s.Remaining.String(), heapMB)
70+
71+
return res
72+
}
73+
74+
// MetricsStatus renders ProgressStatus metrics as a string.
75+
func MetricsStatus(s ProgressStatus) string {
76+
metrics := ""
77+
78+
for _, m := range s.Metrics {
79+
switch m.Type {
80+
case ProgressBytes:
81+
spdMBPS := float64(atomic.LoadInt64(m.Value)) / (s.Elapsed.Seconds() * 1024 * 1024)
82+
metrics += fmt.Sprintf("%s: %.1f MB/s, ", m.Name, spdMBPS)
83+
case ProgressDuration:
84+
metrics += m.Name + ": " + time.Duration(atomic.LoadInt64(m.Value)).String() + ", "
85+
case ProgressGauge:
86+
metrics += fmt.Sprintf("%s: %d, ", m.Name, atomic.LoadInt64(m.Value))
87+
}
88+
}
89+
90+
if metrics != "" {
91+
metrics = metrics[:len(metrics)-2]
92+
}
93+
94+
return metrics
95+
}
96+
97+
// Start spawns background progress reporter.
98+
func (p *Progress) Start(total int64, current func() int64, task string) {
99+
p.done = make(chan bool)
100+
atomic.StoreInt64(&p.lines, 0)
101+
p.task = task
102+
p.current = current
103+
104+
interval := p.Interval
105+
if interval == 0 {
106+
interval = time.Second
107+
}
108+
109+
p.prnt = p.Print
110+
if p.prnt == nil {
111+
p.prnt = func(s ProgressStatus) {
112+
println(DefaultStatus(s))
113+
}
114+
}
115+
116+
p.start = time.Now()
117+
p.tot = float64(total)
118+
done := p.done
119+
t := time.NewTicker(interval)
120+
121+
go func() {
122+
for {
123+
select {
124+
case <-t.C:
125+
p.printStatus(false)
126+
127+
case <-done:
128+
t.Stop()
129+
130+
return
131+
}
132+
}
133+
}()
134+
}
135+
136+
// AddMetrics adds more metrics to progress status message.
137+
func (p *Progress) AddMetrics(metrics ...ProgressMetric) {
138+
p.metrics = append(p.metrics, metrics...)
139+
}
140+
141+
func (p *Progress) printStatus(last bool) {
142+
s := ProgressStatus{}
143+
s.Task = p.task
144+
s.LinesCompleted = atomic.LoadInt64(&p.lines)
145+
s.Metrics = p.metrics
146+
147+
b := float64(p.current())
148+
s.DonePercent = 100 * b / p.tot
149+
s.Elapsed = time.Since(p.start)
150+
s.SpeedMBPS = (b / s.Elapsed.Seconds()) / (1024 * 1024)
151+
s.SpeedLPS = float64(s.LinesCompleted) / s.Elapsed.Seconds()
152+
153+
s.Remaining = time.Duration(float64(100*s.Elapsed)/s.DonePercent) - s.Elapsed
154+
s.Remaining = s.Remaining.Truncate(time.Second)
155+
156+
if s.Remaining > 100*time.Millisecond || last {
157+
p.prnt(s)
158+
}
159+
}
160+
161+
// CountLine increments line counter.
162+
func (p *Progress) CountLine() int64 {
163+
return atomic.AddInt64(&p.lines, 1)
164+
}
165+
166+
// Lines returns number of counted lines.
167+
func (p *Progress) Lines() int64 {
168+
return atomic.LoadInt64(&p.lines)
169+
}
170+
171+
// Stop stops progress reporting.
172+
func (p *Progress) Stop() {
173+
p.printStatus(true)
174+
p.metrics = nil
175+
176+
close(p.done)
177+
}
178+
179+
// CountingReader wraps io.Reader to count bytes.
180+
type CountingReader struct {
181+
Reader io.Reader
182+
183+
readBytes int64
184+
}
185+
186+
// Read reads and counts bytes.
187+
func (cr *CountingReader) Read(p []byte) (n int, err error) {
188+
n, err = cr.Reader.Read(p)
189+
190+
atomic.AddInt64(&cr.readBytes, int64(n))
191+
192+
return n, err
193+
}
194+
195+
// Bytes returns number of read bytes.
196+
func (cr *CountingReader) Bytes() int64 {
197+
return atomic.LoadInt64(&cr.readBytes)
198+
}
199+
200+
// CountingWriter wraps io.Writer to count bytes.
201+
type CountingWriter struct {
202+
Writer io.Writer
203+
204+
writtenBytes int64
205+
}
206+
207+
// Write writes and counts bytes.
208+
func (cr *CountingWriter) Write(p []byte) (n int, err error) {
209+
n, err = cr.Writer.Write(p)
210+
211+
atomic.AddInt64(&cr.writtenBytes, int64(n))
212+
213+
return n, err
214+
}
215+
216+
// Bytes returns number of written bytes.
217+
func (cr *CountingWriter) Bytes() int64 {
218+
return atomic.LoadInt64(&cr.writtenBytes)
219+
}
220+
221+
// MetricsExposer provides metric counters.
222+
type MetricsExposer interface {
223+
Metrics() []ProgressMetric
224+
}

run_me.sh

-27
This file was deleted.

0 commit comments

Comments
 (0)