mirror of https://github.com/tailscale/tailscale/
util/clientmetrics: add new package to add metrics to the client
And annotate magicsock as a start. And add localapi and debug handlers with the Prometheus-format exporter. Updates #3307 Change-Id: I47c5d535fe54424741df143d052760387248f8d3 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>pull/3311/head
parent
c5d572f371
commit
57b039c51d
@ -0,0 +1,135 @@
|
|||||||
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package clientmetric provides client-side metrics whose values
|
||||||
|
// get occasionally logged.
|
||||||
|
package clientmetric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
metrics = map[string]*Metric{}
|
||||||
|
sortedDirty bool
|
||||||
|
sorted []*Metric
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type is a metric type: counter or gauge.
|
||||||
|
type Type uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeGauge Type = iota
|
||||||
|
TypeCounter
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metric is an integer metric value that's tracked over time.
|
||||||
|
//
|
||||||
|
// It's safe for concurrent use.
|
||||||
|
type Metric struct {
|
||||||
|
v int64 // atomic; the metric value
|
||||||
|
name string
|
||||||
|
|
||||||
|
lastLogv int64 // v atomic, epoch seconds
|
||||||
|
lastLog int64 // atomic, epoch seconds
|
||||||
|
logSec int // log every N seconds max
|
||||||
|
typ Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metric) Name() string { return m.name }
|
||||||
|
func (m *Metric) Value() int64 { return atomic.LoadInt64(&m.v) }
|
||||||
|
func (m *Metric) Type() Type { return m.typ }
|
||||||
|
|
||||||
|
// Add increments m's value by n.
|
||||||
|
//
|
||||||
|
// If m is of type counter, n should not be negative.
|
||||||
|
func (m *Metric) Add(n int64) {
|
||||||
|
atomic.AddInt64(&m.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets m's value to v.
|
||||||
|
//
|
||||||
|
// If m is of type counter, Set should not be used.
|
||||||
|
func (m *Metric) Set(v int64) {
|
||||||
|
atomic.StoreInt64(&m.v, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish registers a metric in the global map.
|
||||||
|
// It panics if the name is a duplicate anywhere in the process.
|
||||||
|
func (m *Metric) Publish() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if m.name == "" {
|
||||||
|
panic("unnamed Metric")
|
||||||
|
}
|
||||||
|
if _, dup := metrics[m.name]; dup {
|
||||||
|
panic("duplicate metric " + m.name)
|
||||||
|
}
|
||||||
|
metrics[m.name] = m
|
||||||
|
sortedDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics returns the sorted list of metrics.
|
||||||
|
//
|
||||||
|
// The returned slice should not be mutated.
|
||||||
|
func Metrics() []*Metric {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if sortedDirty {
|
||||||
|
sortedDirty = false
|
||||||
|
sorted = make([]*Metric, 0, len(metrics))
|
||||||
|
for _, m := range metrics {
|
||||||
|
sorted = append(sorted, m)
|
||||||
|
}
|
||||||
|
sort.Slice(sorted, func(i, j int) bool {
|
||||||
|
return sorted[i].name < sorted[j].name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnpublished initializes a new Metric without calling Publish on
|
||||||
|
// it.
|
||||||
|
func NewUnpublished(name string, typ Type) *Metric {
|
||||||
|
return &Metric{
|
||||||
|
name: name,
|
||||||
|
typ: typ,
|
||||||
|
logSec: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCounter returns a new metric that can only increment.
|
||||||
|
func NewCounter(name string) *Metric {
|
||||||
|
m := NewUnpublished(name, TypeCounter)
|
||||||
|
m.Publish()
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGauge returns a new metric that can both increment and decrement.
|
||||||
|
func NewGauge(name string) *Metric {
|
||||||
|
m := NewUnpublished(name, TypeGauge)
|
||||||
|
m.Publish()
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePrometheusExpositionFormat writes all client metrics to w in
|
||||||
|
// the Prometheus text-based exposition format.
|
||||||
|
//
|
||||||
|
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
|
||||||
|
func WritePrometheusExpositionFormat(w io.Writer) {
|
||||||
|
for _, m := range Metrics() {
|
||||||
|
switch m.Type() {
|
||||||
|
case TypeGauge:
|
||||||
|
fmt.Fprintf(w, "# TYPE %s gauge\n", m.Name())
|
||||||
|
case TypeCounter:
|
||||||
|
fmt.Fprintf(w, "# TYPE %s counter\n", m.Name())
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s %v\n", m.Name(), m.Value())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue