Initial commit
This commit is contained in:
commit
b1d3992f35
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 VictoriaMetrics
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
26
README.md
Normal file
26
README.md
Normal file
@ -0,0 +1,26 @@
|
||||
[![Build Status](https://travis-ci.org/VictoriaMetrics/metrics.svg)](https://travis-ci.org/VictoriaMetrics/metrics)
|
||||
[![GoDoc](https://godoc.org/github.com/VictoriaMetrics/metrics?status.svg)](http://godoc.org/github.com/VictoriaMetrics/metrics)
|
||||
[![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/metrics)](https://goreportcard.com/report/github.com/VictoriaMetrics/metrics)
|
||||
[![codecov](https://codecov.io/gh/VictoriaMetrics/metrics/branch/master/graph/badge.svg)](https://codecov.io/gh/VictoriaMetrics/metrics)
|
||||
|
||||
# metrics - lightweight alternative to `github.com/prometheus/client_golang/prometheus`.
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Lightweight. Has minimal number of third-party dependencies and all these deps are small.
|
||||
See [this article](https://medium.com/@valyala/stripping-dependency-bloat-in-victoriametrics-docker-image-983fb5912b0d) for details.
|
||||
* Easy to use. See the [API docs](http://godoc.org/github.com/VictoriaMetrics/metrics).
|
||||
* Fast.
|
||||
|
||||
|
||||
### Limitations
|
||||
|
||||
* It doesn't implement advanced functionality from [github.com/prometheus/client_golang/prometheus](https://godoc.org/github.com/prometheus/client_golang/prometheus).
|
||||
|
||||
|
||||
### Users
|
||||
|
||||
* `Metrics` has been extracted from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) sources.
|
||||
See [this article](https://medium.com/devopslinks/victoriametrics-creating-the-best-remote-storage-for-prometheus-5d92d66787ac)
|
||||
for more info about `VictoriaMetrics`.
|
3
go.mod
Normal file
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/VictoriaMetrics/metrics
|
||||
|
||||
require github.com/valyala/histogram v1.0.1
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
|
||||
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg=
|
||||
github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto=
|
225
metrics.go
Normal file
225
metrics.go
Normal file
@ -0,0 +1,225 @@
|
||||
// Package metrics implements Prometheus-compatible metrics for applications.
|
||||
//
|
||||
// This package is similar to https://github.com/prometheus/client_golang ,
|
||||
// but is simpler to use and is more lightweight.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/histogram"
|
||||
)
|
||||
|
||||
type gauge struct {
|
||||
f func() float64
|
||||
}
|
||||
|
||||
func (g *gauge) marshalTo(prefix string, w io.Writer) {
|
||||
v := g.f()
|
||||
fmt.Fprintf(w, "%s %g\n", prefix, v)
|
||||
}
|
||||
|
||||
// NewGauge creates a gauge with the given name, which calls f
|
||||
// to obtain gauge value.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// f must be safe for concurrent calls.
|
||||
func NewGauge(name string, f func() float64) {
|
||||
g := &gauge{
|
||||
f: f,
|
||||
}
|
||||
registerMetric(name, g)
|
||||
}
|
||||
|
||||
// Counter is a counter.
|
||||
//
|
||||
// It may be used as a gauge if Dec and Set are called.
|
||||
type Counter struct {
|
||||
n uint64
|
||||
}
|
||||
|
||||
// NewCounter creates and returns new counter with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
func NewCounter(name string) *Counter {
|
||||
c := &Counter{}
|
||||
registerMetric(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
func registerMetric(name string, m metric) {
|
||||
if err := validateMetric(name); err != nil {
|
||||
// Do not use logger.Panicf here, since it may be uninitialized yet.
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
metricsMapLock.Lock()
|
||||
ok := isRegisteredMetric(metricsMap, name)
|
||||
if !ok {
|
||||
nm := namedMetric{
|
||||
name: name,
|
||||
metric: m,
|
||||
}
|
||||
metricsMap = append(metricsMap, nm)
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
if ok {
|
||||
// Do not use logger.Panicf here, since it may be uninitialized yet.
|
||||
panic(fmt.Errorf("BUG: metric with name %q is already registered", name))
|
||||
}
|
||||
}
|
||||
|
||||
// Inc increments c.
|
||||
func (c *Counter) Inc() {
|
||||
atomic.AddUint64(&c.n, 1)
|
||||
}
|
||||
|
||||
// Dec decrements c.
|
||||
func (c *Counter) Dec() {
|
||||
atomic.AddUint64(&c.n, ^uint64(0))
|
||||
}
|
||||
|
||||
// Add adds n to c.
|
||||
func (c *Counter) Add(n int) {
|
||||
atomic.AddUint64(&c.n, uint64(n))
|
||||
}
|
||||
|
||||
// Get returns the current value for c.
|
||||
func (c *Counter) Get() uint64 {
|
||||
return atomic.LoadUint64(&c.n)
|
||||
}
|
||||
|
||||
// Set sets c value to n.
|
||||
func (c *Counter) Set(n uint64) {
|
||||
atomic.StoreUint64(&c.n, n)
|
||||
}
|
||||
|
||||
// marshalTo marshals c with the given prefix to w.
|
||||
func (c *Counter) marshalTo(prefix string, w io.Writer) {
|
||||
v := c.Get()
|
||||
fmt.Fprintf(w, "%s %d\n", prefix, v)
|
||||
}
|
||||
|
||||
var (
|
||||
metricsMapLock sync.Mutex
|
||||
metricsMap []namedMetric
|
||||
)
|
||||
|
||||
type namedMetric struct {
|
||||
name string
|
||||
metric metric
|
||||
}
|
||||
|
||||
func isRegisteredMetric(mm []namedMetric, name string) bool {
|
||||
for _, nm := range mm {
|
||||
if nm.name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sortMetrics(mm []namedMetric) {
|
||||
lessFunc := func(i, j int) bool {
|
||||
return mm[i].name < mm[j].name
|
||||
}
|
||||
if !sort.SliceIsSorted(mm, lessFunc) {
|
||||
sort.Slice(mm, lessFunc)
|
||||
}
|
||||
}
|
||||
|
||||
type metric interface {
|
||||
marshalTo(prefix string, w io.Writer)
|
||||
}
|
||||
|
||||
// WritePrometheus writes all the registered metrics in Prometheus format to w.
|
||||
//
|
||||
// If exposeProcessMetrics is true, then various `go_*` metrics are exposed
|
||||
// for the current process.
|
||||
func WritePrometheus(w io.Writer, exposeProcessMetrics bool) {
|
||||
// Export user-defined metrics.
|
||||
metricsMapLock.Lock()
|
||||
sortMetrics(metricsMap)
|
||||
for _, nm := range metricsMap {
|
||||
nm.metric.marshalTo(nm.name, w)
|
||||
}
|
||||
metricsMapLock.Unlock()
|
||||
|
||||
if !exposeProcessMetrics {
|
||||
return
|
||||
}
|
||||
|
||||
// Export memory stats.
|
||||
var ms runtime.MemStats
|
||||
runtime.ReadMemStats(&ms)
|
||||
fmt.Fprintf(w, `go_memstats_alloc_bytes %d`+"\n", ms.Alloc)
|
||||
fmt.Fprintf(w, `go_memstats_alloc_bytes_total %d`+"\n", ms.TotalAlloc)
|
||||
fmt.Fprintf(w, `go_memstats_buck_hash_sys_bytes %d`+"\n", ms.BuckHashSys)
|
||||
fmt.Fprintf(w, `go_memstats_frees_total %d`+"\n", ms.Frees)
|
||||
fmt.Fprintf(w, `go_memstats_gc_cpu_fraction %f`+"\n", ms.GCCPUFraction)
|
||||
fmt.Fprintf(w, `go_memstats_gc_sys_bytes %d`+"\n", ms.GCSys)
|
||||
fmt.Fprintf(w, `go_memstats_heap_alloc_bytes %d`+"\n", ms.HeapAlloc)
|
||||
fmt.Fprintf(w, `go_memstats_heap_idle_bytes %d`+"\n", ms.HeapIdle)
|
||||
fmt.Fprintf(w, `go_memstats_heap_inuse_bytes %d`+"\n", ms.HeapInuse)
|
||||
fmt.Fprintf(w, `go_memstats_heap_objects %d`+"\n", ms.HeapObjects)
|
||||
fmt.Fprintf(w, `go_memstats_heap_released_bytes %d`+"\n", ms.HeapReleased)
|
||||
fmt.Fprintf(w, `go_memstats_heap_sys_bytes %d`+"\n", ms.HeapSys)
|
||||
fmt.Fprintf(w, `go_memstats_last_gc_time_seconds %f`+"\n", float64(ms.LastGC)/1e9)
|
||||
fmt.Fprintf(w, `go_memstats_lookups_total %d`+"\n", ms.Lookups)
|
||||
fmt.Fprintf(w, `go_memstats_mallocs_total %d`+"\n", ms.Mallocs)
|
||||
fmt.Fprintf(w, `go_memstats_mcache_inuse_bytes %d`+"\n", ms.MCacheInuse)
|
||||
fmt.Fprintf(w, `go_memstats_mcache_sys_bytes %d`+"\n", ms.MCacheSys)
|
||||
fmt.Fprintf(w, `go_memstats_mspan_inuse_bytes %d`+"\n", ms.MSpanInuse)
|
||||
fmt.Fprintf(w, `go_memstats_mspan_sys_bytes %d`+"\n", ms.MSpanSys)
|
||||
fmt.Fprintf(w, `go_memstats_next_gc_bytes %d`+"\n", ms.NextGC)
|
||||
fmt.Fprintf(w, `go_memstats_other_sys_bytes %d`+"\n", ms.OtherSys)
|
||||
fmt.Fprintf(w, `go_memstats_stack_inuse_bytes %d`+"\n", ms.StackInuse)
|
||||
fmt.Fprintf(w, `go_memstats_stack_sys_bytes %d`+"\n", ms.StackSys)
|
||||
fmt.Fprintf(w, `go_memstats_sys_bytes %d`+"\n", ms.Sys)
|
||||
|
||||
fmt.Fprintf(w, `go_cgo_calls_count %d`+"\n", runtime.NumCgoCall())
|
||||
fmt.Fprintf(w, `go_cpu_count %d`+"\n", runtime.NumCPU())
|
||||
|
||||
gcPauses := histogram.NewFast()
|
||||
for _, pauseNs := range ms.PauseNs[:] {
|
||||
gcPauses.Update(float64(pauseNs) / 1e9)
|
||||
}
|
||||
phis := []float64{0, 0.25, 0.5, 0.75, 1}
|
||||
quantiles := make([]float64, 0, len(phis))
|
||||
for i, q := range gcPauses.Quantiles(quantiles[:0], phis) {
|
||||
fmt.Fprintf(w, `go_gc_duration_seconds{quantile="%g"} %f`+"\n", phis[i], q)
|
||||
}
|
||||
fmt.Fprintf(w, `go_gc_duration_seconds_sum %f`+"\n", float64(ms.PauseTotalNs)/1e9)
|
||||
fmt.Fprintf(w, `go_gc_duration_seconds_count %d`+"\n", ms.NumGC)
|
||||
fmt.Fprintf(w, `go_gc_forced_count %d`+"\n", ms.NumForcedGC)
|
||||
|
||||
fmt.Fprintf(w, `go_gomaxprocs %d`+"\n", runtime.GOMAXPROCS(0))
|
||||
fmt.Fprintf(w, `go_goroutines %d`+"\n", runtime.NumGoroutine())
|
||||
numThread, _ := runtime.ThreadCreateProfile(nil)
|
||||
fmt.Fprintf(w, `go_threads %d`+"\n", numThread)
|
||||
|
||||
// Export build details.
|
||||
fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version())
|
||||
fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n",
|
||||
runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT())
|
||||
}
|
||||
|
||||
var startTime = time.Now()
|
145
summary.go
Normal file
145
summary.go
Normal file
@ -0,0 +1,145 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/histogram"
|
||||
)
|
||||
|
||||
const defaultSummaryWindow = 5 * time.Minute
|
||||
|
||||
var defaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1}
|
||||
|
||||
// Summary implements summary.
|
||||
type Summary struct {
|
||||
mu sync.Mutex
|
||||
|
||||
curr *histogram.Fast
|
||||
next *histogram.Fast
|
||||
|
||||
quantiles []float64
|
||||
quantileValues []float64
|
||||
}
|
||||
|
||||
// NewSummary creates and returns new summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func NewSummary(name string) *Summary {
|
||||
return NewSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
}
|
||||
|
||||
// NewSummaryExt creates and returns new summary with the given name,
|
||||
// window and quantiles.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible lables.
|
||||
// For instance,
|
||||
//
|
||||
// * foo
|
||||
// * foo{bar="baz"}
|
||||
// * foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
s := &Summary{
|
||||
curr: histogram.NewFast(),
|
||||
next: histogram.NewFast(),
|
||||
quantiles: quantiles,
|
||||
quantileValues: make([]float64, len(quantiles)),
|
||||
}
|
||||
registerSummary(s, window)
|
||||
registerMetric(fmt.Sprintf("\x00%s", name), s)
|
||||
for i, q := range quantiles {
|
||||
quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q))
|
||||
qv := &quantileValue{
|
||||
s: s,
|
||||
idx: i,
|
||||
}
|
||||
registerMetric(quantileValueName, qv)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Update updates the summary.
|
||||
func (s *Summary) Update(v float64) {
|
||||
s.mu.Lock()
|
||||
s.curr.Update(v)
|
||||
s.next.Update(v)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// UpdateDuration updates request duration based on the given startTime.
|
||||
func (s *Summary) UpdateDuration(startTime time.Time) {
|
||||
d := time.Since(startTime).Seconds()
|
||||
s.Update(d)
|
||||
}
|
||||
|
||||
func (s *Summary) marshalTo(prefix string, w io.Writer) {
|
||||
// Just update s.quantileValues and don't write anything to w.
|
||||
// s.quantileValues will be marshaled later via quantileValue.marshalTo.
|
||||
s.updateQuantiles()
|
||||
}
|
||||
|
||||
func (s *Summary) updateQuantiles() {
|
||||
s.mu.Lock()
|
||||
s.quantileValues = s.curr.Quantiles(s.quantileValues[:0], s.quantiles)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
type quantileValue struct {
|
||||
s *Summary
|
||||
idx int
|
||||
}
|
||||
|
||||
func (qv *quantileValue) marshalTo(prefix string, w io.Writer) {
|
||||
qv.s.mu.Lock()
|
||||
v := qv.s.quantileValues[qv.idx]
|
||||
qv.s.mu.Unlock()
|
||||
fmt.Fprintf(w, "%s %g\n", prefix, v)
|
||||
}
|
||||
|
||||
func addTag(name, tag string) string {
|
||||
if len(name) == 0 || name[len(name)-1] != '}' {
|
||||
return fmt.Sprintf("%s{%s}", name, tag)
|
||||
}
|
||||
return fmt.Sprintf("%s, %s}", name[:len(name)-1], tag)
|
||||
}
|
||||
|
||||
func registerSummary(s *Summary, window time.Duration) {
|
||||
summariesLock.Lock()
|
||||
summaries[window] = append(summaries[window], s)
|
||||
if len(summaries[window]) == 1 {
|
||||
go summariesSwapCron(window)
|
||||
}
|
||||
summariesLock.Unlock()
|
||||
}
|
||||
|
||||
func summariesSwapCron(window time.Duration) {
|
||||
for {
|
||||
time.Sleep(window / 2)
|
||||
summariesLock.Lock()
|
||||
for _, s := range summaries[window] {
|
||||
s.mu.Lock()
|
||||
tmp := s.curr
|
||||
s.curr = s.next
|
||||
s.next = tmp
|
||||
s.next.Reset()
|
||||
s.mu.Unlock()
|
||||
}
|
||||
summariesLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
summaries = map[time.Duration][]*Summary{}
|
||||
summariesLock sync.Mutex
|
||||
)
|
81
validator.go
Normal file
81
validator.go
Normal file
@ -0,0 +1,81 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func validateMetric(s string) error {
|
||||
if len(s) == 0 {
|
||||
return fmt.Errorf("metric cannot be empty")
|
||||
}
|
||||
if s[0] == 0 {
|
||||
// Skip special case metrics. See Histogram for details.
|
||||
return nil
|
||||
}
|
||||
n := strings.IndexByte(s, '{')
|
||||
if n < 0 {
|
||||
return validateIdent(s)
|
||||
}
|
||||
ident := s[:n]
|
||||
s = s[n+1:]
|
||||
if err := validateIdent(ident); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) == 0 || s[len(s)-1] != '}' {
|
||||
return fmt.Errorf("missing closing curly brace at the end of %q", ident)
|
||||
}
|
||||
return validateTags(s[:len(s)-1])
|
||||
}
|
||||
|
||||
func validateTags(s string) error {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
n := strings.IndexByte(s, '=')
|
||||
if n < 0 {
|
||||
return fmt.Errorf("missing `=` after %q", s)
|
||||
}
|
||||
ident := s[:n]
|
||||
s = s[n+1:]
|
||||
if err := validateIdent(ident); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return fmt.Errorf("missing starting `\"` for %q value; tail=%q", ident, s)
|
||||
}
|
||||
s = s[1:]
|
||||
again:
|
||||
n = strings.IndexByte(s, '"')
|
||||
if n < 0 {
|
||||
return fmt.Errorf("missing trailing `\"` for %q value; tail=%q", ident, s)
|
||||
}
|
||||
m := n
|
||||
for m > 0 && s[m-1] == '\\' {
|
||||
m--
|
||||
}
|
||||
if (n-m)%2 == 1 {
|
||||
s = s[n+1:]
|
||||
goto again
|
||||
}
|
||||
s = s[n+1:]
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasPrefix(s, ", ") {
|
||||
return fmt.Errorf("missing `, ` after %q value; tail=%q", ident, s)
|
||||
}
|
||||
s = s[2:]
|
||||
}
|
||||
}
|
||||
|
||||
func validateIdent(s string) error {
|
||||
if !identRegexp.MatchString(s) {
|
||||
return fmt.Errorf("invalid identifier %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var identRegexp = regexp.MustCompile("^[a-zA-Z_:][a-zA-Z0-9_:]*$")
|
60
validator_test.go
Normal file
60
validator_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateMetricSuccess(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
if err := validateMetric(s); err != nil {
|
||||
t.Fatalf("cannot validate %q: %s", s, err)
|
||||
}
|
||||
}
|
||||
f("a")
|
||||
f("_9:8")
|
||||
f("a{}")
|
||||
f(`a{foo="bar"}`)
|
||||
f(`foo{bar="baz", x="y\"z"}`)
|
||||
f(`foo{bar="b}az"}`)
|
||||
}
|
||||
|
||||
func TestValidateMetricError(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
if err := validateMetric(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error when validating %q", s)
|
||||
}
|
||||
}
|
||||
f("")
|
||||
f("{}")
|
||||
|
||||
// superflouos space
|
||||
f("a ")
|
||||
f(" a")
|
||||
f(" a ")
|
||||
f("a {}")
|
||||
f("a{} ")
|
||||
f("a{ }")
|
||||
f(`a{foo ="bar"}`)
|
||||
f(`a{ foo="bar"}`)
|
||||
f(`a{foo= "bar"}`)
|
||||
f(`a{foo="bar" }`)
|
||||
f(`a{foo="bar",baz="a"}`)
|
||||
f(`a{foo="bar" ,baz="a"}`)
|
||||
|
||||
// invalid tags
|
||||
f("a{foo}")
|
||||
f("a{=}")
|
||||
f(`a{=""}`)
|
||||
f(`a{`)
|
||||
f(`a}`)
|
||||
f(`a{foo=}`)
|
||||
f(`a{foo="`)
|
||||
f(`a{foo="}`)
|
||||
f(`a{foo="bar",}`)
|
||||
f(`a{foo="bar", x`)
|
||||
f(`a{foo="bar", x=`)
|
||||
f(`a{foo="bar", x="`)
|
||||
f(`a{foo="bar", x="}`)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user