Skip to content

Type safe, CPU & memory efficient metric builder for VictoriaMetrics.

License

Notifications You must be signed in to change notification settings

wazazaby/vimebu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

37ec28c · Mar 15, 2025
Dec 18, 2023
Oct 12, 2023
Sep 23, 2023
Sep 16, 2024
Sep 16, 2024
Sep 16, 2024
Dec 19, 2024
Mar 15, 2025
Mar 15, 2025
Sep 6, 2024
Mar 21, 2024

Repository files navigation

vimebu

CI Go Reference Go Report Card MIT License

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

Usage

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

// Only using the builder.
var requestsTotalCounter = metrics.NewCounter(
    vimebu.
        Metric("request_total").
        LabelString("path", "/foo/bar").
        String(), // request_total{path="/foo/bar"}
)

// Registering the metric using the provided helpers.
var updateTotalCounterV3 = vimebu.
    Metric("update_total").
    LabelInt("version", 3).
    NewCounter() // update_total{version="3"}

Create metrics with variable label values

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

import (
    "net"

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

func getCassandraQueryCounter(name string, host net.IP, err error) *metrics.Counter {
    return vimebu.Metric("cassandra_query_total").
        LabelString("name", name).
        LabelStringer("host", host).
        LabelErrorQuote("error", err). // The label "error" won't be added if err is nil.
        GetOrCreateCounter() // cassandra_query_total{name="beep",host="1.2.3.4",error="i/o timeout"}
}

Create metrics with conditional labels

You can also have metrics with labels that are added under certain conditions.

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

func getHTTPRequestCounter(host string) *metrics.Counter {
    builder := vimebu.Metric("api_http_requests_total")
    if host != "" {
        builder.LabelString("host", host)
    }
    return builder.GetOrCreateCounter() // api_http_requests_total or api_http_requests_total{host="api.app.com"}
}

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
import (
    "github.com/VictoriaMetrics/metrics"
    "github.com/wazazaby/vimebu/v2"
)

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

Create metrics with label values that aren't strings

You can use these methods to append specific value types to the builder :

  • Builder.LabelBool for booleans
  • Builder.LabelInt and variations for signed integers
  • Builder.LabelUint and variations for unsigned integers
  • Builder.LabelFloat and variations for floats
  • Builder.LabelStringer for values implementing the fmt.Stringer interface
  • Builder.LabelError for values implementing the error interface

Benchmark comparison

Here are some simple benchmarks comparing building a metric using the fmt package vs vimebu. Each metric is built with 4 labels (string, int, error and bool).

As you can see, in the sequential benchmarks, vimebu is about twice as fast. For the parralel benchmarks, vimebu is about ~30% faster.

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
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

Under the hood

Builders can be acquired and released using a BuilderPool, which is a wrapper around a sync.Pool instance. A default BuilderPool instance is created and exposed by the package, it is accessible like this :

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

func getHTTPRequestCounter(path string) *metrics.Counter {
    builder := vimebu.AcquireBuilder()
    defer vimebu.ReleaseBuilder(builder)

    builder.LabelQuote("path", path)
    return builder.GetOrCreateCounter() // api_http_requests_total{path="some/bro\"ken/path"}
}

Using a pool allows for reusing objects, thus relieving pressure on the garbage collector.

Understanding that this syntax can be quite verbose, vimebu also provides a simpler API that manages the lifecycle of these objects internally by using the vimebu.Metric package level function. Here, vimebu will automatically acquire a Builder, to finally reset and release it when the Builder.String method is called.

Concurrency notes

  • A Builder instance is not safe to use from concurrently running goroutines
  • A Builder instance must not be copied (it embeds a noOp sync.Locker implementation, to raise warnings with go vet when copied)