1package obs
2
3import (
4 "errors"
5 "sync"
6 "time"
7
8 "github.com/prometheus/client_golang/prometheus"
9)
10
11var errCommit = errors.New("obs: a commit is 40 or 64 lowercase hexadecimal digits")
12
13var (
14 commitInfoDesc = prometheus.NewDesc("tug_config_commit_info",
15 "The configuration in force: its source, and for a git source the applied commit.", []string{"source", "commit"}, nil)
16 lastFetchDesc = prometheus.NewDesc("tug_config_last_fetch_success_timestamp_seconds",
17 "When the git source last fetched its remote successfully, in Unix seconds.", nil, nil)
18 rollbackDesc = prometheus.NewDesc("tug_config_rollback_refused_total",
19 "Fetched commits refused because the applied commit is not their ancestor.", nil, nil)
20)
21
22type ConfigSource struct {
23 mu sync.Mutex
24 source string
25 commit string
26 fetched time.Time
27 refused uint64
28}
29
30func (r *Registry) ConfigSource(source string) *ConfigSource {
31 if r == nil {
32 return nil
33 }
34 if source != "file" && source != "git" {
35 source = "unknown"
36 }
37 c := &ConfigSource{source: source}
38 r.reg.MustRegister(c)
39 return c
40}
41
42func (c *ConfigSource) Applied(commit string) error {
43 if c == nil {
44 return nil
45 }
46 if !validCommit(commit) {
47 return errCommit
48 }
49 c.mu.Lock()
50 defer c.mu.Unlock()
51 c.commit = commit
52 return nil
53}
54
55func validCommit(s string) bool {
56 if len(s) != 40 && len(s) != 64 {
57 return false
58 }
59 for i := 0; i < len(s); i++ {
60 if !('0' <= s[i] && s[i] <= '9' || 'a' <= s[i] && s[i] <= 'f') {
61 return false
62 }
63 }
64 return true
65}
66
67func (c *ConfigSource) Fetched(at time.Time) {
68 if c == nil {
69 return
70 }
71 c.mu.Lock()
72 defer c.mu.Unlock()
73 c.fetched = at
74}
75
76func (c *ConfigSource) RollbackRefused() {
77 if c == nil {
78 return
79 }
80 c.mu.Lock()
81 defer c.mu.Unlock()
82 c.refused++
83}
84
85func (c *ConfigSource) Describe(ch chan<- *prometheus.Desc) {
86 ch <- commitInfoDesc
87 ch <- lastFetchDesc
88 ch <- rollbackDesc
89}
90
91func (c *ConfigSource) Collect(ch chan<- prometheus.Metric) {
92 c.mu.Lock()
93 source, commit, fetched, refused := c.source, c.commit, c.fetched, c.refused
94 c.mu.Unlock()
95 ch <- prometheus.MustNewConstMetric(commitInfoDesc, prometheus.GaugeValue, 1, source, commit)
96 if !fetched.IsZero() {
97 ch <- prometheus.MustNewConstMetric(lastFetchDesc, prometheus.GaugeValue, float64(fetched.UnixNano())/1e9)
98 }
99 ch <- prometheus.MustNewConstMetric(rollbackDesc, prometheus.CounterValue, float64(refused))
100}