Skip to content

Commit 81bb71f

Browse files
authored
Add compressed output support (#17)
1 parent 5d8e8de commit 81bb71f

5 files changed

Lines changed: 73 additions & 11 deletions

File tree

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ linters-settings:
1515
unparam:
1616
check-exported: true
1717
funlen:
18-
lines: 80
18+
lines: 90
1919
cyclop:
2020
max-complexity: 16
2121

cmd/catp/catp/app.go

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ type runner struct {
3737
totalBytes int64
3838
outDir string
3939

40-
parallel int
41-
currentBytes int64
42-
currentLines int64
40+
parallel int
41+
42+
currentBytes int64
43+
currentBytesUncompressed int64
44+
currentLines int64
4345

4446
// pass is a slice of OR items, that are slices of AND items.
4547
pass [][][]byte
@@ -49,7 +51,9 @@ type runner struct {
4951
currentFile *progress.CountingReader
5052
currentTotal int64
5153

52-
lastErr error
54+
lastErr error
55+
lastStatusTime int64
56+
lastBytesUncompressed int64
5357
}
5458

5559
// st renders Status as a string.
@@ -70,6 +74,9 @@ func (r *runner) st(s progress.Status) string {
7074
Status: s,
7175
}
7276

77+
currentBytesUncompressed := atomic.LoadInt64(&r.currentBytesUncompressed)
78+
currentBytes := atomic.LoadInt64(&r.currentBytes)
79+
7380
if len(r.sizes) > 1 && r.parallel <= 1 {
7481
pr.CurrentFilePercent = 100 * float64(r.currentFile.Bytes()) / float64(r.currentTotal)
7582

@@ -82,6 +89,20 @@ func (r *runner) st(s progress.Status) string {
8289
s.Elapsed.Round(10*time.Millisecond).String(), s.Remaining.String())
8390
}
8491

92+
if currentBytesUncompressed > currentBytes {
93+
lastBytesUncompressed := atomic.LoadInt64(&r.lastBytesUncompressed)
94+
lastStatusTime := atomic.LoadInt64(&r.lastStatusTime)
95+
now := time.Now().Unix()
96+
97+
if lastBytesUncompressed != 0 && lastStatusTime != 0 && lastStatusTime != now {
98+
spdMPBS := (float64(currentBytesUncompressed-lastBytesUncompressed) / float64(now-lastStatusTime)) / (1024 * 1024)
99+
res = strings.ReplaceAll(res, "MB/s", fmt.Sprintf("MB/s (uncomp %.1f MB/s)", spdMPBS))
100+
}
101+
102+
atomic.StoreInt64(&r.lastStatusTime, now)
103+
atomic.StoreInt64(&r.lastBytesUncompressed, currentBytesUncompressed)
104+
}
105+
85106
if len(r.pass) > 0 || len(r.skip) > 0 {
86107
m := atomic.LoadInt64(&r.matches)
87108
pr.Matches = &m
@@ -91,7 +112,7 @@ func (r *runner) st(s progress.Status) string {
91112
if r.progressJSON != "" {
92113
pr.ElapsedSeconds = pr.Elapsed.Truncate(time.Second).Seconds()
93114
pr.RemainingSeconds = pr.Remaining.Round(time.Second).Seconds()
94-
pr.BytesCompleted = atomic.LoadInt64(&r.currentBytes)
115+
pr.BytesCompleted = currentBytes
95116
pr.BytesTotal = atomic.LoadInt64(&r.totalBytes)
96117

97118
if j, err := json.Marshal(pr); err == nil {
@@ -241,6 +262,13 @@ func (r *runner) cat(filename string) (err error) {
241262
return err
242263
}
243264

265+
crl := progress.NewCountingReader(rd)
266+
267+
crl.SetBytes(&r.currentBytesUncompressed)
268+
crl.SetLines(&r.currentLines)
269+
270+
rd = crl
271+
244272
out := r.output
245273

246274
if r.outDir != "" {
@@ -358,7 +386,7 @@ func (i *stringFlags) Set(value string) error {
358386
}
359387

360388
// Main is the entry point for catp CLI tool.
361-
func Main() error { //nolint:funlen,cyclop,gocognit,gocyclo
389+
func Main() error { //nolint:funlen,cyclop,gocognit,gocyclo,maintidx
362390
var (
363391
pass stringFlags
364392
skip stringFlags
@@ -382,7 +410,7 @@ func Main() error { //nolint:funlen,cyclop,gocognit,gocyclo
382410

383411
cpuProfile := flag.String("dbg-cpu-prof", "", "write first 10 seconds of CPU profile to file")
384412
memProfile := flag.String("dbg-mem-prof", "", "write heap profile to file after 10 seconds")
385-
output := flag.String("output", "", "output to file instead of STDOUT")
413+
output := flag.String("output", "", "output to file (can have .gz or .zst ext for compression) instead of STDOUT")
386414
noProgress := flag.Bool("no-progress", false, "disable progress printing")
387415
progressJSON := flag.String("progress-json", "", "write current progress to a file")
388416
ver := flag.Bool("version", false, "print version and exit")
@@ -424,19 +452,45 @@ func Main() error { //nolint:funlen,cyclop,gocognit,gocyclo
424452
}
425453

426454
if *output != "" && r.outDir == "" { //nolint:nestif
427-
out, err := os.Create(*output)
455+
fn := *output
456+
457+
out, err := os.Create(fn) //nolint:gosec
428458
if err != nil {
429-
return fmt.Errorf("failed to create output file %s: %w", *output, err)
459+
return fmt.Errorf("failed to create output file %s: %w", fn, err)
430460
}
431461

432-
w := bufio.NewWriterSize(out, 64*1024)
462+
r.output = out
463+
compCloser := io.Closer(io.NopCloser(nil))
464+
465+
switch {
466+
case strings.HasSuffix(fn, ".gz"):
467+
gw := gzip.NewWriter(r.output)
468+
compCloser = gw
469+
470+
r.output = gw
471+
case strings.HasSuffix(fn, ".zst"):
472+
zw, err := zstdWriter(r.output)
473+
if err != nil {
474+
return fmt.Errorf("zstd new writer: %w", err)
475+
}
476+
477+
compCloser = zw
478+
479+
r.output = zw
480+
}
481+
482+
w := bufio.NewWriterSize(r.output, 64*1024)
433483
r.output = w
434484

435485
defer func() {
436486
if err := w.Flush(); err != nil {
437487
log.Fatalf("failed to flush STDOUT buffer: %s", err)
438488
}
439489

490+
if err := compCloser.Close(); err != nil {
491+
log.Fatalf("failed to close compressor: %s", err)
492+
}
493+
440494
if err := out.Close(); err != nil {
441495
log.Fatalf("failed to close output file %s: %s", *output, err)
442496
}

cmd/catp/catp/cgo_zstd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ func init() {
1515
func zstdReader(rd io.Reader) (io.Reader, error) {
1616
return zstd.NewReader(rd), nil
1717
}
18+
19+
func zstdWriter(w io.Writer) (io.WriteCloser, error) {
20+
return zstd.NewWriterLevel(w, zstd.BestSpeed), nil
21+
}

cmd/catp/catp/zstd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ func zstdReader(rd io.Reader) (io.Reader, error) {
1616
return nil, fmt.Errorf("failed to init zst reader: %w", err)
1717
}
1818
}
19+
20+
func zstdWriter(w io.Writer) (io.WriteCloser, error) {
21+
return zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedFastest), zstd.WithLowerEncoderMem(true))
22+
}

cmd/catp/default.pgo

6.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)