Featured image by Egon Elbre
Custom Errors
- You can export custom errors in your package and use the
errors
package to create them.
import "errors"
var (
ErrNotANumber = errors.New("Classic NaN issue")
ErrFailedToOpen = errors.New("Door is shut")
)
// Convention is to prefix with Err (or err if private)
- Convention is to use
err
for the variable name receiving a potentially returned error. - You can use
fmt.Fprintln
to output an error toSTDERR
.
fmt.Fprintln(os.Stderr, err)
- Use
fmt.Errorf
to create new formatted errors which may also contain an existing error to be “unwrapped” with the%w
specifier..
func something() error {
return fmt.Errorf("Encountered error %w because of %d", ErrFailedToOpen, 42)
}
err := something()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
CSV file support
- Go has a standard library package for working with CSV files.
- Reading from a CSV file.
import "encoding/csv"
f, err := os.Open(fname)
// handle err and defer closing of file
csvReader := csv.NewReader(f) // takes an io.Reader
// Read all records into memory as [][]string
data, err := csvReader.ReadAll()
// there is also Read to read a single row at a time
- Writing to an io.Writer.
writer := csv.NewWriter(os.Stdout)
writer.WriteAll(data) // data = [][]string
// or
writer.Write(row) // row = []string
Benchmarking
- On Unix style shells you can use the
time
command to time how long an operation took. NOTE: However the format of the Zsh built-in is different to the output of Bash. This can be changed by specifying the TIMEFMT environment variable. See this SO post.
$ TIMEFMT=$'real\t%E\nuser\t%U\nsys\t%S'
$ time sleep 2
real 2.01s
user 0.00s
sys 0.00s
- In Go you can provide benchmarking functions as part of the unit-tests.
func someBenchmark(b *testing.B) {
// prepare some inputs etc.
b.ResetTimer() // This ensure the setup time above is not part of the timings
for i := 0; i < b.N; i++ {
// code to be benchmarked
}
}
- To run benchmarks you use
go test
and with the-bench regexp
argument. It is good to ensure no unit-tests are run by using-run ^$
(so that no unit-test match the regexp).
$ go test -bench . -run ^$
- b.N is adjusted so that the benchmark runs for roughly about 1 second. You can use
-benchtime=
argument to adjust this.
$ go test -bench . -benchtime=20x -run ^$ | tee results.txt
Profiling
- Go comes with built-in profiling and tracing tools.
- To perform a CPU profile while running a bench mark.
$ go test -bench . -benchtime=10x -run ^$ -cpuprofile cpu00.pprof
$ go tool pprof cpu00.pprof
Type: cpu
Time: May 3, 2022 at 7:56pm (BST)
Duration: 3.63s, Total samples = 3.67s (101.09%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
# enter 'top' to see where most of the time is spend
(pprof) top
Showing nodes accounting for 3.59s, 97.82% of 3.67s total
Dropped 29 nodes (cum <= 0.02s)
Showing top 10 nodes out of 82
flat flat% sum% cum cum%
2.99s 81.47% 81.47% 2.99s 81.47% syscall.syscall
0.21s 5.72% 87.19% 0.21s 5.72% runtime.madvise
0.12s 3.27% 90.46% 0.12s 3.27% runtime.pthread_cond_wait
...
# Use 'top -cum' to see cummalitive time being spent
(pprof) top -cum
# Use 'list someFunction' to see source code listing and time spent on which lines.
(pprof) list myPackage.thisTakesTooLong
- To perform a memory profile.
$ go test -bench . -benchtime=10x -run ^$ -memprofile mem00.pprof
$ go tool pprof -alloc_space mem00.pprof
(pprof) top -cum
...
2.02GB 40.36% 75.11% 3.21GB 64.18% encoding/csv.(*Reader).ReadAll
- To run a benchmark and also get a summary of allocations being made.
$ go test -bench . -benchtime=10x -run ^$ -benchmem | tee benchresults00m.txt
...
someBenchmark-8 10 311644288 ns/op 495584000 B/op 5041044 allocs/op
- pprof can generate a nice call / usage graph when you enter
web
at the pprof prompt. However you will need to have graphviz installed. (brew install graphviz
). - To compare two benchmarks you can use the “benchstat” tool.
# To install it
$ go install golang.org/x/perf/cmd/benchstat@latest
# To compare two benchmarks
$ ~/go/bin/benchstat results00.txt results01.txt
Tracing
- This is awesome! Go provides a tool for tracing where a program spends its time, which CPUs are used etc. It uses your default browser to visualise and interact with.
$ go test -bench . -benchtime=10x -run ^$ -trace trace01.out
$ go tool trace trace01.out
2022/05/03 20:35:40 Parsing trace...
2022/05/03 20:35:41 Splitting trace...
2022/05/03 20:35:41 Opening browser. Trace viewer is listening on http://127.0.0.1:51549
Miscellaneous
- See package
strconv
for function to help convert between strings and data types. E.g. strconv.ParseFloat(). io.Discard
is anio.Writer
that can be used for when you don’t care about the write operations. For example instead of passing io.Stdout to a function you can let it discard the output while unit-testing.iotest.TimeoutReader
can be used to simulate a timeout error while unit-testing.- You can make a channel of empty struct to signal when an event has occurred. This avoids extra memory allocation for the channel.
isDone := make(chan struct{})
// in some go routine
close(isDone)
// listen for signal
for {
switch {
case <- isDone:
// received signal
}
}
- To get the number of CPU cores available use
runtime.NumCPU()
.