From d77ebb8511e04859bb77bed95054ce59b024039f Mon Sep 17 00:00:00 2001 From: j75689 Date: Mon, 28 Nov 2022 22:09:27 +0800 Subject: [PATCH] metrics: add label type in metrics package --- metrics/exp/exp.go | 70 ++++++++++++++++++++++++++++++++ metrics/label.go | 48 ++++++++++++++++++++++ metrics/prometheus/collector.go | 10 +++++ metrics/prometheus/prometheus.go | 2 + metrics/registry.go | 2 +- 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 metrics/label.go diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 3ebe8cc68..4d9304a5f 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -95,6 +95,20 @@ func (exp *exp) getFloat(name string) *expvar.Float { return v } +func (exp *exp) getMap(name string) *expvar.Map { + var v *expvar.Map + exp.expvarLock.Lock() + p := expvar.Get(name) + if p != nil { + v = p.(*expvar.Map) + } else { + v = new(expvar.Map) + expvar.Publish(name, v) + } + exp.expvarLock.Unlock() + return v +} + func (exp *exp) publishCounter(name string, metric metrics.Counter) { v := exp.getInt(name) v.Set(metric.Count()) @@ -162,6 +176,60 @@ func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer exp.getInt(name + ".99-percentile").Set(ps[3]) } +func (exp *exp) publishLabel(name string, metric metrics.Label) { + labels := metric.Value() + for k, v := range labels { + exp.getMap(name).Set(k, exp.interfaceToExpVal(v)) + } +} + +func (exp *exp) interfaceToExpVal(v interface{}) expvar.Var { + switch i := v.(type) { + case string: + newV := new(expvar.String) + newV.Set(i) + return newV + case int64: + newV := new(expvar.Int) + newV.Set(i) + return newV + case int32: + newV := new(expvar.Int) + newV.Set(int64(i)) + return newV + case int16: + newV := new(expvar.Int) + newV.Set(int64(i)) + return newV + case int8: + newV := new(expvar.Int) + newV.Set(int64(i)) + return newV + case int: + newV := new(expvar.Int) + newV.Set(int64(i)) + return newV + case float32: + newV := new(expvar.Float) + newV.Set(float64(i)) + return newV + case float64: + newV := new(expvar.Float) + newV.Set(i) + return newV + case map[string]interface{}: + newV := new(expvar.Map) + for k, v := range i { + newV.Set(k, exp.interfaceToExpVal(v)) + } + return newV + default: + newV := new(expvar.String) + newV.Set(fmt.Sprint(v)) + return newV + } +} + func (exp *exp) syncToExpvar() { exp.registry.Each(func(name string, i interface{}) { switch i := i.(type) { @@ -179,6 +247,8 @@ func (exp *exp) syncToExpvar() { exp.publishTimer(name, i) case metrics.ResettingTimer: exp.publishResettingTimer(name, i) + case metrics.Label: + exp.publishLabel(name, i) default: panic(fmt.Sprintf("unsupported type for '%s': %T", name, i)) } diff --git a/metrics/label.go b/metrics/label.go new file mode 100644 index 000000000..6c6b02ed7 --- /dev/null +++ b/metrics/label.go @@ -0,0 +1,48 @@ +package metrics + +import "encoding/json" + +// Label hold an map[string]interface{} value that can be set arbitrarily. +type Label interface { + Value() map[string]interface{} + String() string + Mark(map[string]interface{}) +} + +// NewRegisteredLabel constructs and registers a new StandardLabel. +func NewRegisteredLabel(name string, r Registry) Label { + c := NewStandardLabel() + if nil == r { + r = DefaultRegistry + } + r.Register(name, c) + return c +} + +// NewStandardLabel constructs a new StandardLabel. +func NewStandardLabel() *StandardLabel { + return &StandardLabel{} +} + +// StandardLabel is the standard implementation of a Label. +type StandardLabel struct { + value map[string]interface{} + jsonStr string +} + +// Value returns label values. +func (l *StandardLabel) Value() map[string]interface{} { + return l.value +} + +// Mark records the label. +func (l *StandardLabel) Mark(value map[string]interface{}) { + buf, _ := json.Marshal(value) + l.jsonStr = string(buf) + l.value = value +} + +// String returns label by JSON format. +func (l *StandardLabel) String() string { + return l.jsonStr +} diff --git a/metrics/prometheus/collector.go b/metrics/prometheus/collector.go index 3959cbf5e..25c7b4f4c 100644 --- a/metrics/prometheus/collector.go +++ b/metrics/prometheus/collector.go @@ -31,6 +31,7 @@ var ( typeSummaryTpl = "# TYPE %s summary\n" keyValueTpl = "%s %v\n\n" keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n" + keyLabelValueTpl = "%s%s %v\n\n" ) // collector is a collection of byte buffers that aggregate Prometheus reports @@ -98,6 +99,15 @@ func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) { c.buff.WriteRune('\n') } +func (c *collector) addLabel(name string, m metrics.Label) { + c.writeLabel(mutateKey(name), m.String()) +} + +func (c *collector) writeLabel(name string, value interface{}) { + c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name)) + c.buff.WriteString(fmt.Sprintf(keyLabelValueTpl, name, value, 1)) +} + func (c *collector) writeGaugeCounter(name string, value interface{}) { name = mutateKey(name) c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name)) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 9ad5ec7e9..2b94266cb 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -57,6 +57,8 @@ func Handler(reg metrics.Registry) http.Handler { c.addTimer(name, m.Snapshot()) case metrics.ResettingTimer: c.addResettingTimer(name, m.Snapshot()) + case metrics.Label: + c.addLabel(name, m) default: log.Warn("Unknown Prometheus metric type", "type", fmt.Sprintf("%T", i)) } diff --git a/metrics/registry.go b/metrics/registry.go index c5435adf2..8ab519fcf 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -196,7 +196,7 @@ func (r *StandardRegistry) register(name string, i interface{}) error { return DuplicateMetric(name) } switch i.(type) { - case Counter, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer, ResettingTimer: + case Counter, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer, ResettingTimer, Label: r.metrics[name] = i } return nil