Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,23 @@ tasks:
- goreleaser release --snapshot --clean

# Test
test:
desc: Run full coverage
cmds:
- task: test:race
- task: test:check

test:unit:
desc: Run all tests
desc: Run all unit tests
cmds:
- go test ./...
- go test -tags=pcre2 github.com/zix99/rare/pkg/matchers/fastregex

test:race:
desc: Run tests with race
cmds:
- go test -race ./...

test:acceptance:
desc: Run acceptance tests
cmds:
Expand Down
5 changes: 3 additions & 2 deletions cmd/heatmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/zix99/rare/pkg/testutil"
)

func TestHeatmap(t *testing.T) {
Expand All @@ -17,12 +18,12 @@ func TestHeatmapLinear(t *testing.T) {
out, eout, err := testCommandCapture(heatmapCommand(), `--snapshot -m "(.+) (.+)" -e "{$ {1} {2}}" testdata/heat.txt`)
assert.NoError(t, err)
assert.Empty(t, eout)
assert.Contains(t, out, " - 0 2 1 4 2 6 3 9 4\n a..\nx -22\ny 224\nz 9--\nMatched: 10 / 10 (R: 3; C: 3)\n39 B (0 B/s)")
testutil.AssertPattern(t, out, " - 0 2 1 4 2 6 3 9 4\n a..\nx -22\ny 224\nz 9--\nMatched: 10 / 10 (R: 3; C: 3)\n39 B in * (*)\n")
}

func TestHeatmapLog2(t *testing.T) {
out, eout, err := testCommandCapture(heatmapCommand(), `--snapshot -m "(.+) (.+)" -e "{$ {1} {2}}" --scale log2 testdata/heat.txt`)
assert.NoError(t, err)
assert.Empty(t, eout)
assert.Contains(t, out, " - 1 4 2 7 3 9 4\n a..\nx ---\ny --4\nz 9--\nMatched: 10 / 10 (R: 3; C: 3)\n39 B (0 B/s)")
testutil.AssertPattern(t, out, " - 1 4 2 7 3 9 4\n a..\nx ---\ny --4\nz 9--\nMatched: 10 / 10 (R: 3; C: 3)\n39 B in * (*)\n")
}
3 changes: 2 additions & 1 deletion cmd/histo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/zix99/rare/pkg/testutil"
)

// Exercise common commands for panics
Expand All @@ -21,7 +22,7 @@ func TestHistogram(t *testing.T) {
func TestHistogramRender(t *testing.T) {
out, eout, err := testCommandCapture(histogramCommand(), `--snapshot -m "(\d+)" -e "{bucket {1} 10}" testdata/log.txt`)
assert.NoError(t, err)
assert.Equal(t, out, "0 2 \n20 1 \n\n\n\nMatched: 3 / 6 (Groups: 2)\n96 B (0 B/s) \n")
testutil.AssertPattern(t, out, "0 2 \n20 1 \n\n\n\nMatched: 3 / 6 (Groups: 2)\n96 B in * (*)\n")
assert.Equal(t, "", eout)
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/reduce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestReduce(t *testing.T) {
`-m (\d+) --snapshot -a "test={sumi {.} {0}}" testdata/log.txt`)
assert.NoError(t, err)
assert.Empty(t, eout)
assert.Equal(t, "test: 32\nMatched: 3 / 6\n96 B (0 B/s) \n", out)
testutil.AssertPattern(t, out, "test: 32\nMatched: 3 / 6\n96 B in * (*)\n")
}

func TestReduceBasics(t *testing.T) {
Expand Down
83 changes: 66 additions & 17 deletions pkg/extractor/batchers/batcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ type Batcher struct {
errorCount int
activeFiles []string

startTime, stopTime time.Time

// Atomic fields (only used to compute performance metrics)
readBytes uint64
readBytes atomic.Uint64

// Used only in StatusString to compute read rate
lastRateUpdate time.Time
lastRate, lastRateBytes uint64
}
Expand All @@ -37,11 +41,16 @@ func newBatcher(bufferSize int) *Batcher {
return &Batcher{
c: make(chan extractor.InputBatch, bufferSize),
lastRateUpdate: time.Now(),
startTime: time.Now(),
}
}

func (s *Batcher) close() {
close(s.c)

s.mux.Lock()
s.stopTime = time.Now()
s.mux.Unlock()
}

func (s *Batcher) BatchChan() <-chan extractor.InputBatch {
Expand Down Expand Up @@ -80,11 +89,11 @@ func (s *Batcher) incErrors() {
}

func (s *Batcher) incReadBytes(n uint64) {
atomic.AddUint64(&s.readBytes, n)
s.readBytes.Add(n)
}

func (s *Batcher) ReadBytes() uint64 {
return atomic.LoadUint64(&s.readBytes)
return s.readBytes.Load()
}

func (s *Batcher) ReadFiles() int {
Expand All @@ -106,46 +115,86 @@ func (s *Batcher) ActiveFileCount() int {
}

// StatusString gets a formatted version of the current reader-set
// [9/10 !1] 1.41 GB in 2.7s (~526.71 MB/s) | a b c (and 2 more...)
func (s *Batcher) StatusString() string {
var sb strings.Builder
sb.Grow(100)
const maxFilesToWrite = 2

s.mux.Lock()
defer s.mux.Unlock()

// Total files read
if s.sourceCount > 1 {
sb.WriteString(fmt.Sprintf("[%d/%d] ", s.readCount, s.sourceCount))
sb.WriteString(fmt.Sprintf("[%d/%d", s.readCount, s.sourceCount))
// Errors
if s.errorCount > 0 {
sb.WriteString(fmt.Sprintf(" !%d", s.errorCount))
}
sb.WriteString("] ")
}

// Rate / bytes
readBytes := atomic.LoadUint64(&s.readBytes)
sb.WriteString(humanize.ByteSize(readBytes) + " ")
// Total read bytes
readBytes := s.readBytes.Load()
sb.WriteString(humanize.ByteSize(readBytes))

// Elapsed time
elapsed := s.elapsedTimeNoLock()
sb.WriteString(" in " + durationToString(elapsed))

// Read rate
if s.stopTime.IsZero() {
// Progress
elapsedTime := time.Since(s.lastRateUpdate).Seconds()
if elapsedTime >= 0.5 {
s.lastRate = uint64(float64(readBytes-s.lastRateBytes) / elapsedTime)
s.lastRateBytes = readBytes
s.lastRateUpdate = time.Now()
}

elapsedTime := time.Since(s.lastRateUpdate).Seconds()
if elapsedTime >= 0.5 {
s.lastRate = uint64(float64(s.readBytes-s.lastRateBytes) / elapsedTime)
s.lastRateBytes = s.readBytes
s.lastRateUpdate = time.Now()
sb.WriteString(" (" + humanize.ByteSize(s.lastRate) + "/s)")
} else {
// Final
rate := uint64(float64(readBytes) / elapsed.Seconds())
sb.WriteString(" (~" + humanize.ByteSize(rate) + "/s)")
}

sb.WriteString("(" + humanize.ByteSize(s.lastRate) + "/s) ")

// Current actively read files
writeFiles := min(len(s.activeFiles), maxFilesToWrite)
if writeFiles > 0 {
sb.WriteString("| ")
sb.WriteString(" | ")
sb.WriteString(strings.Join(s.activeFiles[:writeFiles], ", "))

if len(s.activeFiles) > maxFilesToWrite {
sb.WriteString(fmt.Sprintf(" (and %d more...)", len(s.activeFiles)-maxFilesToWrite))
}
}

s.mux.Unlock()

return sb.String()
}

func (s *Batcher) elapsedTimeNoLock() time.Duration {
if s.stopTime.IsZero() {
return time.Since(s.startTime)
}
return s.stopTime.Sub(s.startTime)
}

// Variable duration pretty-printing
// Optimize to prevent terminal stutter/length changes (eg 2.1 2.11...)
func durationToString(d time.Duration) string {
switch {
case d < time.Second:
return fmt.Sprintf("%03dms", d.Milliseconds())
case d < time.Minute:
return fmt.Sprintf("%.02fs", d.Truncate(10*time.Millisecond).Seconds())
case d < time.Hour:
return fmt.Sprintf("%dm%.1fs", int(d.Truncate(time.Minute).Minutes()), (d % time.Minute).Seconds())
default:
return d.Truncate(time.Second).String()
}
}

// syncReaderToBatcher reads a reader buffer and breaks up its scans to `batchSize`
//
// and writes the batch-sized results to a channel
Expand Down
11 changes: 11 additions & 0 deletions pkg/extractor/batchers/batcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,14 @@ line3`
assert.Equal(t, s.errorCount, 0)
assert.Equal(t, s.ReadBytes(), uint64(17))
}

func TestDurationFormat(t *testing.T) {
assert.Equal(t, "020ms", durationToString(20*time.Millisecond))
assert.Equal(t, "1.12s", durationToString(1120*time.Millisecond))
assert.Equal(t, "1.10s", durationToString(1100*time.Millisecond))
assert.Equal(t, "12.10s", durationToString(12100*time.Millisecond))
assert.Equal(t, "35m2.2s", durationToString(35*time.Minute+2222*time.Millisecond))
assert.Equal(t, "2h5m2s", durationToString(125*time.Minute+2*time.Second))
assert.Equal(t, "2h5m2s", durationToString(125*time.Minute+2222*time.Millisecond))
assert.Equal(t, "30h0m2s", durationToString(60*30*time.Minute+2222*time.Millisecond))
}
49 changes: 49 additions & 0 deletions pkg/testutil/asserts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package testutil

import (
"fmt"
"regexp"
"strings"
"testing"
)

// Simplified regex-check that only supports '*' (multi-char) and '?' (single-char) wildcards in the pattern
func AssertPattern(t *testing.T, str, pattern string) {
t.Helper()
if err := matchesPattern(pattern, str); err != nil {
t.Error(err)
}
}

func matchesPattern(pattern, str string) error {
p, err := rewritePatternToRegex(pattern)
if err != nil {
return fmt.Errorf("invalid pattern: %w", err)
}

if !p.MatchString(str) {
return fmt.Errorf("'%s' does not match pattern '%s'", str, pattern)
}

return nil
}

func rewritePatternToRegex(pattern string) (*regexp.Regexp, error) {
var sb strings.Builder
sb.Grow(len(pattern))

sb.WriteRune('^')
for _, r := range pattern {
switch r {
case '*':
sb.WriteString("(.*)")
case '?':
sb.WriteString(".")
default:
sb.WriteString(regexp.QuoteMeta(string(r)))
}
}
sb.WriteRune('$')

return regexp.Compile(sb.String())
}
19 changes: 19 additions & 0 deletions pkg/testutil/asserts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package testutil

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMatcher(t *testing.T) {
assert.NoError(t, matchesPattern("hi", "hi"))
assert.NoError(t, matchesPattern("hello *!", "hello bob!"))
assert.NoError(t, matchesPattern("hello m?n", "hello mon"))
assert.NoError(t, matchesPattern("hello m?n(", "hello mon("))

assert.Error(t, matchesPattern("hi", "bye"))
assert.Error(t, matchesPattern("hello m?n", "hello moon"))
assert.Error(t, matchesPattern("hello *", "bye bob"))
assert.Error(t, matchesPattern("hello *!", "hello bob?"))
}
Loading