dspc

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 20, 2024 License: MIT Imports: 11 Imported by: 0

README

DSPC GoDoc

DSPC - a dead simple progress counter for concurrent CLI apps in Go.

Think of it as a set of named atomic counters that are:

  • Fast - lock and allocation free, faster than map[string]int in both single-threaded and concurrent scenarios
  • Nice to look at - clean, readable terminal output that updates in-place
  • Log-friendly - don't interfere with your application's log output
  • Minimalistic - no dependencies, no configuration, tiny API

Installation

go get -u github.com/destel/dspc

Quick Start

// Create an instance. Zero value is ready to use
var progress dspc.Progress

// Start printing progress to stdout every second
defer progress.PrettyPrintEvery(os.Stdout, 1*time.Second, "Progress:")()


// Then, in worker goroutines just increment/decrement/set counters as needed 
progress.Inc("ok", 1)
progress.Inc("errors", 1)
progress.Inc("skipped", 1)

Check out a complete example here.

When to Use

This library is a good fit for CLI applications that do concurrent work. When running tasks across multiple goroutines, you usually need to track their progress - the number of tasks that are completed, failed or currently in progress. You may also want to track dynamic categories - different kinds of tasks, or types of errors (e.g., "validation_error", "network_error", etc).

When running the app in terminal, you want to see a clean progress report that updates in real-time, while keeping your normal application logs readable and separate.

Another example is running such apps in Kubernetes. For simple one-off pods, instead of configuring metrics and dashboards, you may just want to watch the logs and progress reports in real-time with kubectl logs -f.

DSPC can also help to debug concurrent applications too. Add a few counters across your goroutines to see which ones are making progress and which ones are stuck.

If such use cases sound familiar, DSPC is what you need.

Not a Good Fit For

  • Long-running services/daemons
  • Large number of counters that don't fit on a single screen
  • Apps with high-frequency logging (e.g., logs every 10ms) - progress updates may get lost in the log stream
  • Complex metrics - your monitoring needs are not covered by simple counters/gauges

Performance

cpu: Apple M2 Max
BenchmarkSingleThreaded/Map-12         135504824    8.911 ns/op    0 B/op    0 allocs/op
BenchmarkSingleThreaded/Progress-12    145009293    8.554 ns/op    0 B/op    0 allocs/op

BenchmarkMultiThreaded/Map-12                           10755136     110.7 ns/op      0 B/op    0 allocs/op
BenchmarkMultiThreaded/Progress-12                      40967048      47.00 ns/op     0 B/op    0 allocs/op
BenchmarkMultiThreaded/Progress_w_disjoint_keys-12    1000000000       1.035 ns/op    0 B/op    0 allocs/op

BenchmarkPrinting-12    2464957    467.8 ns/op    0 B/op    0 allocs/op

Documentation

Overview

Package dspc provides tools for tracking progress of concurrent operations in a terminal.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Progress

type Progress struct {
	// contains filtered or unexported fields
}

Progress tracks multiple named counters. It's similar to a concurrent map[string]int64 but optimized for progress tracking for a small stable sets of keys (typically fitting on a single screen).

All operations are atomic, lock-free and safe for concurrent use. Methods do not allocate memory in the hot path.

The zero Progress is empty and ready for use

func (*Progress) All

func (p *Progress) All() iter.Seq2[string, int64]

All returns an iterator over all counters in lexicographical key order. The iterator yields (key, value) pairs. The values represent atomic snapshots of the counters at the time they are read.

func (*Progress) Get

func (p *Progress) Get(key string) int64

Get returns the current value of the counter associated with key. Returns 0 if the key doesn't exist.

func (*Progress) Inc

func (p *Progress) Inc(key string, delta int64)

Inc atomically adds delta to the counter associated with the given key. If the key doesn't exist, it's created with an initial value of 0 before adding delta.

func (*Progress) PrettyPrintEvery

func (p *Progress) PrettyPrintEvery(w io.Writer, t time.Duration, title string) func()

PrettyPrintEvery periodically prints the current state of Progress to w (typically stdout ot stderr). It updates the output in-place and won't damage the log output of the application (assuming logs are printed line by line). PrettyPrintEvery returns the function that stops the printing when called.

Usage:

stop := progress.PrettyPrintEvery(os.Stdout, time.Second, "Progress:")
defer stop()

Or better:

defer progress.PrettyPrintEvery(os.Stdout, time.Second, "Progress:")()

Example output:

Progress:
  completed  15
  failed      3
  skipped     7

func (*Progress) Set

func (p *Progress) Set(key string, value int64)

Set atomically sets the counter associated with the given key to value. If the key doesn't exist, it's created with the specified value.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL