This repository has been archived by the owner on Jan 17, 2024. It is now read-only.
forked from tenjin/go-metrics-prometheus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
promex.go
118 lines (107 loc) · 2.74 KB
/
promex.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package promex
import (
"fmt"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/rcrowley/go-metrics"
)
type Exporter struct {
namespace string
subsystem string
srcRegistry metrics.Registry
targetRegistry prometheus.Registerer
flushInterval time.Duration
gauges map[string]prometheus.Gauge
_ struct{}
}
func NewExporter(
namespace string,
subsystem string,
srcRegistry metrics.Registry,
targetRegistry prometheus.Registerer,
flushInterval time.Duration,
) *Exporter {
return &Exporter{
namespace: ReplaceUnsafeKeyCharacters(namespace),
subsystem: ReplaceUnsafeKeyCharacters(subsystem),
srcRegistry: srcRegistry,
targetRegistry: targetRegistry,
flushInterval: flushInterval,
gauges: make(map[string]prometheus.Gauge),
}
}
func (e *Exporter) Run() {
for _ = range time.Tick(e.flushInterval) {
e.ExportOnce()
}
}
func (e *Exporter) ExportOnce() {
for metricName, values := range e.srcRegistry.GetAll() {
for valueName, value := range values {
e.getOrRegisterPrometheusGauge(metricName, valueName).Set(AsFloat64(value))
}
}
}
func (e *Exporter) getOrRegisterPrometheusGauge(metricName, valueName string) prometheus.Gauge {
name := PrometheusMetricName(metricName, valueName)
key := PrometheusMetricKey(e.namespace, e.subsystem, name, valueName)
gauge, ok := e.gauges[key]
if !ok {
gauge = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: e.namespace,
Subsystem: e.subsystem,
Name: name,
})
e.targetRegistry.MustRegister(gauge)
e.gauges[key] = gauge
}
return gauge
}
func AsFloat64(value interface{}) float64 {
switch v := value.(type) {
case float64:
return v
case int64:
return float64(v)
case string: // Go Metrics health checks are sent as error strings
if len(v) > 0 {
return 1
}
return 0
default:
return 0
}
}
func PrometheusMetricName(metric, value string) string {
base := fmt.Sprintf("%s_%s", metric, value)
safe := ReplaceUnsafeKeyCharacters(base)
clean := strings.TrimRight(safe, "_")
return clean
}
func PrometheusMetricKey(namespace, subsystem, metric, valueName string) string {
clean := fmt.Sprintf("%s_%s_%s", namespace, subsystem, PrometheusMetricName(metric, valueName))
return clean
}
func ReplaceUnsafeKeyCharacters(key string) string {
if key == "" {
return "unnamed"
}
bs := []byte(key)
for i := 0; i < len(bs); i++ {
char := bs[i]
// Turn '%' into `p` for when dealing with percentiles.
if char == '%' {
bs[i] = 'p'
continue
}
// All non alphanumerics become underscores.
valid := char >= 'A' && char <= 'Z' || // A-Z is ok
char >= 'a' && char <= 'z' || // a-z is ok
char >= '0' && char <= '9' // 0-9 is ok
if !valid {
bs[i] = '_'
}
}
return string(bs)
}