Skip to content
Open
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
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# vimebu
[![CI](https://github.com/wazazaby/vimebu/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/wazazaby/vimebu/actions/workflows/build-and-test.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/wazazaby/vimebu.svg)](https://pkg.go.dev/github.com/wazazaby/vimebu/v2)
[![Go Reference](https://pkg.go.dev/badge/github.com/wazazaby/vimebu.svg)](https://pkg.go.dev/github.com/wazazaby/vimebu/v3)
[![Go Report Card](https://goreportcard.com/badge/github.com/wazazaby/vimebu)](https://goreportcard.com/report/github.com/wazazaby/vimebu)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/wazazaby/vimebu/blob/master/LICENSE)

vimebu provides a type-safe builder to create VictoriaMetrics compatible metrics.
vimebu provides a type-safe builder to create VictoriaMetrics compatible metrics.

It aims to be as CPU & memory efficient as possible using strategies such as object pooling, buffer reuse etc.

## Installation
`go get -u github.com/wazazaby/vimebu/v2`
`go get -u github.com/wazazaby/vimebu/v3`

## Usage
```go
import (
"github.com/VictoriaMetrics/metrics"
"github.com/wazazaby/vimebu/v2"
"github.com/wazazaby/vimebu/v3"
)

// Only using the builder.
Expand All @@ -35,11 +35,13 @@ var updateTotalCounterV3 = vimebu.

### Create metrics with variable label values
vimebu is even more useful when you want to build metrics with variable label values.

By default, all label values of type `~string` (that is also true for `fmt.Stringer` and `error`) will be quoted using `strconv.AppendQuote` when appended to the builder.
```go
import (
"net"

"github.com/wazazaby/vimebu/v2"
"github.com/wazazaby/vimebu/v3"
)

func getCassandraQueryCounter(name string, host net.IP, err error) *metrics.Counter {
Expand All @@ -56,7 +58,7 @@ You can also have metrics with labels that are added under certain conditions.
```go
import (
"github.com/VictoriaMetrics/metrics"
"github.com/wazazaby/vimebu/v2"
"github.com/wazazaby/vimebu/v3"
)

func getHTTPRequestCounter(host string) *metrics.Counter {
Expand All @@ -68,22 +70,22 @@ func getHTTPRequestCounter(host string) *metrics.Counter {
}
```

### Create metrics with label values that need to be escaped
vimebu also exposes a way to escape quotes on label values you don't control using the following methods :
* `Builder.LabelStringQuote`
* `Builder.LabelStringerQuote`
* `Builder.LabelErrorQuote`
### Create metrics with label values don't need to be escaped
When you control your inputs and want to be as efficient as possible, vimebu also exposes a few methods that simply quotes your strings manually without checking if the content should be escaped :
* `Builder.LabelTrustedString`
* `Builder.LabelTrustedStringer`
* `Builder.LabelTrustedErrorQuote`

```go
import (
"github.com/VictoriaMetrics/metrics"
"github.com/wazazaby/vimebu/v2"
"github.com/wazazaby/vimebu/v3"
)

func getHTTPRequestCounter(path string) *metrics.Counter {
func getHTTPRequestCounter(version string) *metrics.Counter {
return vimebu.Metric("api_http_requests_total").
LabelQuote("path", path).
GetOrCreateCounter() // api_http_requests_total{path="some/bro\"ken/path"}
LabelTrustedString("version", version).
GetOrCreateCounter() // api_http_requests_total{path="v1.2.3"}
}
```

Expand All @@ -108,14 +110,14 @@ In each case, it allocates half as much per operation. Yay!
❯ go test -bench="BenchmarkCompare" -benchmem -run=NONE
goos: darwin
goarch: arm64
pkg: github.com/wazazaby/vimebu/v2
pkg: github.com/wazazaby/vimebu/v3
cpu: Apple M1 Max
BenchmarkCompareSequentialFmt-10 717055 1660 ns/op 1024 B/op 16 allocs/op
BenchmarkCompareParralelFmt-10 2279166 528.0 ns/op 1024 B/op 16 allocs/op
BenchmarkCompareSequentialVimebu-10 1557618 765.0 ns/op 896 B/op 8 allocs/op
BenchmarkCompareParralelVimebu-10 3098870 398.5 ns/op 896 B/op 8 allocs/op
PASS
ok github.com/wazazaby/vimebu/v2 7.445s
ok github.com/wazazaby/vimebu/v3 7.445s
```

### Under the hood
Expand All @@ -125,7 +127,7 @@ A default BuilderPool instance is created and exposed by the package, it is acce
```go
import (
"github.com/VictoriaMetrics/metrics"
"github.com/wazazaby/vimebu/v2"
"github.com/wazazaby/vimebu/v3"
)

func getHTTPRequestCounter(path string) *metrics.Counter {
Expand Down
52 changes: 32 additions & 20 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ func WithLabelNameMaxLen(maxLen int) BuilderOption {
// Only applies to label values added using the following methods :
//
// - [Builder.LabelString]
// - [Builder.LabelStringQuote]
// - [Builder.LabelTrustedString]
// - [Builder.LabelError]
// - [Builder.LabelErrorQuote]
// - [Builder.LabelTrustedError]
// - [Builder.LabelNamedError]
// - [Builder.LabelNamedErrorQuote]
// - [Builder.LabelNamedTrustedError]
//
// Zero means no length limit.
//
Expand Down Expand Up @@ -123,22 +123,25 @@ func (b *Builder) Metric(name string, options ...BuilderOption) *Builder {
}

// LabelString adds a label with a value of type string to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
//
// NoOp if the label name or value are empty.
//
// Panics if [Builder.Metric] hasn't been called on this instance of the [Builder].
func (b *Builder) LabelString(name, value string) *Builder {
return b.labelString(name, value, false)
return b.labelString(name, value, true)
}

// LabelStringQuote adds a label with a value of type string to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
// LabelTrustedString adds a label with a value of type string to the [Builder].
//
// Using this method over the standard one is more efficient as it avoid unecessary compute & allocations,
// the drawback being that your label needs to be safe / escaped already (otherwise, VM will panic).
//
// NoOp if the label name or value are empty.
//
// Panics if [Builder.Metric] hasn't been called on this instance of the [Builder].
func (b *Builder) LabelStringQuote(name, value string) *Builder {
return b.labelString(name, value, true)
func (b *Builder) LabelTrustedString(name, value string) *Builder {
return b.labelString(name, value, false)
}

func (b *Builder) labelString(name, value string, escapeQuotes bool) *Builder {
Expand All @@ -165,6 +168,7 @@ func (b *Builder) labelString(name, value string, escapeQuotes bool) *Builder {
}

// LabelError adds a label with a value implementing the error interface to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
//
// NoOp if the label name is empty, or if err is nil.
//
Expand All @@ -177,6 +181,7 @@ func (b *Builder) LabelError(err error) *Builder {
}

// LabelNamedError adds a label with a value implementing the error interface to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
//
// NoOp if the label name is empty, or if err is nil.
//
Expand All @@ -188,30 +193,34 @@ func (b *Builder) LabelNamedError(name string, err error) *Builder {
return b.LabelString(name, err.Error())
}

// LabelErrorQuote adds a label with a value implementing the error interface to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
// LabelTrustedError adds a label with a value implementing the error interface to the [Builder].
//
// Using this method over the standard one is more efficient as it avoid unecessary compute & allocations,
// the drawback being that your label needs to be safe / escaped already (otherwise, VM will panic).
//
// NoOp if the label name is empty, or if err is nil.
//
// Panics if [Builder.Metric] hasn't been called on this instance of the [Builder].
func (b *Builder) LabelErrorQuote(err error) *Builder {
func (b *Builder) LabelTrustedError(err error) *Builder {
if err == nil {
return b
}
return b.LabelStringQuote(errorLabelName, err.Error())
return b.LabelTrustedString(errorLabelName, err.Error())
}

// LabelNamedErrorQuote adds a label with a value implementing the error interface to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
// LabelNamedTrustedError adds a label with a value implementing the error interface to the [Builder].
//
// Using this method over the standard one is more efficient as it avoid unecessary compute & allocations,
// the drawback being that your label needs to be safe / escaped already (otherwise, VM will panic).
//
// NoOp if the label name is empty, or if err is nil.
//
// Panics if [Builder.Metric] hasn't been called on this instance of the [Builder].
func (b *Builder) LabelNamedErrorQuote(name string, err error) *Builder {
func (b *Builder) LabelNamedTrustedError(name string, err error) *Builder {
if err == nil {
return b
}
return b.LabelStringQuote(name, err.Error())
return b.LabelTrustedString(name, err.Error())
}

// LabelBool adds a label with a value of type bool to the [Builder].
Expand Down Expand Up @@ -371,6 +380,7 @@ func (b *Builder) LabelFloat64(name string, value float64) *Builder {
}

// LabelStringer adds a label with a value implementing the [fmt.Stringer] interface to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
//
// NoOp if the label name is empty, if value is nil, or if the value.String() method call returns an empty string.
//
Expand All @@ -382,17 +392,19 @@ func (b *Builder) LabelStringer(name string, value fmt.Stringer) *Builder {
return b.LabelString(name, value.String())
}

// LabelStringerQuote adds a label with a value implementing the [fmt.Stringer] interface to the [Builder].
// Quotes inside label value will be escaped using [strconv.AppendQuote].
// LabelTrustedStringer adds a label with a value implementing the [fmt.Stringer] interface to the [Builder].
//
// Using this method over the standard one is more efficient as it avoid unecessary compute & allocations,
// the drawback being that your label needs to be safe / escaped already (otherwise, VM will panic).
//
// NoOp if the label name is empty, if value is nil, or if the value.String() method call returns an empty string.
//
// Panics if [Builder.Metric] hasn't been called on this instance of the [Builder].
func (b *Builder) LabelStringerQuote(name string, value fmt.Stringer) *Builder {
func (b *Builder) LabelTrustedStringer(name string, value fmt.Stringer) *Builder {
if value == nil {
return b
}
return b.LabelStringQuote(name, value.String())
return b.LabelTrustedString(name, value.String())
}

// String builds the complete metric by returning the accumulated string.
Expand Down
72 changes: 36 additions & 36 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ func addLabelAnyToBuilder(builder *Builder, label label) {
switch v := label.value.(type) {
case string:
if label.shouldQuote {
builder.LabelStringQuote(label.name, v)
} else {
builder.LabelString(label.name, v)
} else {
builder.LabelTrustedString(label.name, v)
}
case bool:
builder.LabelBool(label.name, v)
Expand Down Expand Up @@ -230,15 +230,15 @@ func addLabelAnyToBuilder(builder *Builder, label label) {
builder.LabelFloat64(label.name, v)
case fmt.Stringer:
if label.shouldQuote {
builder.LabelStringerQuote(label.name, v)
} else {
builder.LabelStringer(label.name, v)
} else {
builder.LabelTrustedStringer(label.name, v)
}
case error:
if label.shouldQuote {
builder.LabelNamedError(label.name, v)
} else {
builder.LabelNamedErrorQuote(label.name, v)
builder.LabelNamedTrustedError(label.name, v)
}
default:
panic(fmt.Sprintf("unsupported type %T", v))
Expand Down Expand Up @@ -298,7 +298,7 @@ func TestBuilderParallel(t *testing.T) {
eg.Go(func() error {
require.NotPanics(t, func() {
Metric(name).
LabelString("host", "foobar").
LabelTrustedString("host", "foobar").
LabelBool("compressed", false).
LabelUint8("port", 80).
LabelFloat32("float", 12.3).
Expand All @@ -325,13 +325,13 @@ func captureLogOutput(f func()) []string {
func TestBuilderOptionsWithLabelNameMaxLen(t *testing.T) {
logLines := captureLogOutput(func() {
metric := Metric("test_options", WithLabelNameMaxLen(3)).
LabelString("one", "one").
LabelString("two", "two").
LabelString("three", "three"). // Will be skipped.
LabelStringQuote("four", `fo"ur`). // Will be skipped.
LabelError(fmt.Errorf("something went wrong")). // Will be skipped.
LabelErrorQuote(fmt.Errorf(`"something" went wrong`)). // Will be skipped.
LabelNamedError("err", fmt.Errorf("mayday")).
LabelTrustedString("one", "one").
LabelTrustedString("two", "two").
LabelTrustedString("three", "three"). // Will be skipped.
LabelString("four", `fo"ur`). // Will be skipped.
LabelTrustedError(fmt.Errorf("something went wrong")). // Will be skipped.
LabelError(fmt.Errorf(`"something" went wrong`)). // Will be skipped.
LabelNamedTrustedError("err", fmt.Errorf("mayday")).
String()
require.Equal(t, `test_options{one="one",two="two",err="mayday"}`, metric)
})
Expand All @@ -342,13 +342,13 @@ func TestBuilderOptionsWithLabelNameMaxLen(t *testing.T) {
func TestBuilderOptionsWithLabelValueMaxLen(t *testing.T) {
logLines := captureLogOutput(func() {
metric := Metric("test_options", WithLabelValueMaxLen(5)).
LabelString("one", "one").
LabelString("two", "two").
LabelString("three", "three").
LabelStringQuote("four", `fo"ur`).
LabelError(fmt.Errorf("something went wrong")). // Will be skipped.
LabelErrorQuote(fmt.Errorf(`"something" went wrong`)). // Will be skipped.
LabelNamedError("err", fmt.Errorf("mayday")). // Will be skipped.
LabelTrustedString("one", "one").
LabelTrustedString("two", "two").
LabelTrustedString("three", "three").
LabelString("four", `fo"ur`).
LabelTrustedError(fmt.Errorf("something went wrong")). // Will be skipped.
LabelError(fmt.Errorf(`"something" went wrong`)). // Will be skipped.
LabelNamedTrustedError("err", fmt.Errorf("mayday")). // Will be skipped.
String()
require.Equal(t, `test_options{one="one",two="two",three="three",four="fo\"ur"}`, metric)
})
Expand Down Expand Up @@ -470,14 +470,14 @@ func BenchmarkCompareSequentialVimebu(b *testing.B) {
)

for range b.N {
_ = Metric("some_metric_name_one").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_two").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_three").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_four").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_one").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_two").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_three").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_four").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_one").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_two").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_three").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_four").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_one").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_two").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_three").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_four").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
}
}

Expand All @@ -493,14 +493,14 @@ func BenchmarkCompareParralelVimebu(b *testing.B) {

b.RunParallel(func(p *testing.PB) {
for p.Next() {
_ = Metric("some_metric_name_one").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_two").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_three").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_four").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_one").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_two").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_three").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_four").LabelString("host", host).LabelInt("version", version).LabelErrorQuote(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_one").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_two").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_three").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_metric_name_four").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_one").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_two").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_three").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
_ = Metric("some_loooooooooooooooooooooooooooooooooonger_metric_name_four").LabelTrustedString("host", host).LabelInt("version", version).LabelError(err).LabelBool("test", test).String()
}
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/wazazaby/vimebu/v2
module github.com/wazazaby/vimebu/v3

go 1.23.0

Expand Down
Loading
Loading