-
Notifications
You must be signed in to change notification settings - Fork 622
Expand file tree
/
Copy pathmetrics.go
More file actions
136 lines (111 loc) · 2.96 KB
/
Copy pathmetrics.go
File metadata and controls
136 lines (111 loc) · 2.96 KB
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package metrics
import (
"context"
"time"
"github.com/rs/zerolog"
"encore.dev/appruntime/exported/config"
"encore.dev/appruntime/shared/shutdown"
"encore.dev/metrics"
)
type Manager struct {
ctx context.Context
cancel func()
static *config.Static
runtime *config.Runtime
reg *metrics.Registry
rootLogger zerolog.Logger
exp exporter
logsEmitter *logsBasedEmitter
}
func NewManager(reg *metrics.Registry, static *config.Static, rtConf *config.Runtime, rootLogger zerolog.Logger) *Manager {
ctx, cancel := context.WithCancel(context.Background())
mgr := &Manager{
ctx: ctx,
cancel: cancel,
reg: reg,
static: static,
runtime: rtConf,
rootLogger: rootLogger,
}
// Metrics aren't configured, return.
if rtConf.Metrics == nil {
return mgr
}
for _, desc := range providerRegistry {
if desc.matches(rtConf.Metrics) {
mgr.exp = desc.newExporter(mgr)
break
}
}
if rtConf.Metrics.LogsBased != nil {
mgr.logsEmitter = newLogsBasedEmitter(rootLogger)
}
return mgr
}
func (mgr *Manager) Shutdown(p *shutdown.Process) error {
// Wait for all services and all tasks to shut down before we shut down metrics.
<-p.ServicesShutdownCompleted.Done()
<-p.OutstandingTasks.Done()
mgr.cancel()
if mgr.exp != nil {
return mgr.exp.Shutdown(p)
}
return nil
}
func (mgr *Manager) BeginCollection() {
if mgr.exp == nil {
return
} else if mgr.runtime.EnvType == "test" {
// Don't collect metrics when running tests.
return
}
interval := mgr.runtime.Metrics.CollectionInterval
if interval <= 0 {
interval = time.Minute
}
timeoutDur := interval / 2
ticker := time.NewTicker(interval)
for {
select {
case <-mgr.ctx.Done():
ticker.Stop()
case <-ticker.C:
ctx, cancel := context.WithTimeout(context.Background(), timeoutDur)
mgr.collectNow(ctx)
cancel()
}
}
}
func (mgr *Manager) collectNow(ctx context.Context) {
if mgr.exp == nil {
return
}
m := mgr.reg.Collect()
// Attach service labels to collected metrics so exporters can enrich
// per-service time series with additional labels (e.g., team, cost_center).
// The same snapshot is shared across all metrics — this is safe because
// exporters only read from it and collection+export is sequential.
if svcLabels := mgr.reg.ServiceLabels(); svcLabels != nil {
for i := range m {
m[i].ServiceLabels = svcLabels
}
}
if err := mgr.exp.Export(ctx, m); err != nil {
mgr.rootLogger.Error().Err(err).Msg("unable to emit metrics")
} else {
mgr.rootLogger.Trace().Int("num_metrics", len(m)).Msg("successfully emitted metrics")
}
}
type exporter interface {
Export(context.Context, []metrics.CollectedMetric) error
Shutdown(p *shutdown.Process) error
}
type providerDesc struct {
name string
matches func(cfg *config.Metrics) bool
newExporter func(m *Manager) exporter
}
var providerRegistry []providerDesc
func registerProvider(desc providerDesc) {
providerRegistry = append(providerRegistry, desc)
}