cosmos-sdk/cosmovisor/errors/multi.go
Emmanuel T Odeke abe3e0c6da
perf: all: remove unnecessary allocations from strings.Builder.WriteString(fmt.Sprintf(...)) (#13230)
This change removes a code pattern that I noticed while on a late night
audit of cosmovisor in which
strings.Builder.WriteString(fmt.Sprintf(...))
calls were being made, yet that's counterproductive to using fmt.Fprintf
which will check whether the writer implements .WriteString and then
avoids the need to firstly build a string using fmt.Sprintf.

The performance wins from this change transcend all dimensions as
exhibited below:

```shell
$ benchstat before.txt after.txt
name            old time/op    new time/op    delta
DetailString-8    5.48µs ±23%    4.40µs ±11%  -19.79%  (p=0.000 n=20+17)

name            old alloc/op   new alloc/op   delta
DetailString-8    2.63kB ± 0%    2.11kB ± 0%  -19.76%  (p=0.000 n=20+20)

name            old allocs/op  new allocs/op  delta
DetailString-8      63.0 ± 0%      50.0 ± 0%  -20.63%  (p=0.000 n=20+20)
```

Fixes #13229
2022-09-09 21:57:46 +00:00

86 lines
2.0 KiB
Go

package errors
import (
"fmt"
"strings"
"github.com/rs/zerolog"
)
// MultiError is an error combining multiple other errors.
// It will never have 0 or 1 errors. It will always have two or more.
type MultiError struct {
errs []error
}
// FlattenErrors possibly creates a MultiError.
// Nil entries are ignored.
// If all provided errors are nil (or nothing is provided), nil is returned.
// If only one non-nil error is provided, it is returned unchanged.
// If two or more non-nil errors are provided, the returned error will be of type *MultiError
// and it will contain each non-nil error.
func FlattenErrors(errs ...error) error {
rv := MultiError{}
for _, err := range errs {
if err != nil {
if merr, isMerr := err.(*MultiError); isMerr {
rv.errs = append(rv.errs, merr.errs...)
} else {
rv.errs = append(rv.errs, err)
}
}
}
switch rv.Len() {
case 0:
return nil
case 1:
return rv.errs[0]
}
return &rv
}
// GetErrors gets all the errors that make up this MultiError.
func (e MultiError) GetErrors() []error {
// Return a copy of the errs slice to prevent alteration of the original slice.
rv := make([]error, e.Len())
copy(rv, e.errs)
return rv
}
// Len gets the number of errors in this MultiError.
func (e MultiError) Len() int {
return len(e.errs)
}
// Error implements the error interface for a MultiError.
func (e *MultiError) Error() string {
var sb strings.Builder
fmt.Fprintf(&sb, "%d errors: ", len(e.errs))
for i, err := range e.errs {
if i != 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, "%d: %v", i+1, err)
}
return sb.String()
}
// String implements the string interface for a MultiError.
func (e MultiError) String() string {
return e.Error()
}
func LogErrors(logger *zerolog.Logger, msg string, err error) {
switch err := err.(type) {
case *MultiError:
if msg != "" {
logger.Error().Msg(msg)
}
for i, e := range err.GetErrors() {
logger.Error().Err(e).Msg(fmt.Sprintf(" %d:", i+1))
}
default:
logger.Error().Err(err).Msg(msg)
}
}