metrics.go

v1.0.0
Doc Versions Source
1
package metrics
2
3
import (
4
	"context"
5
	"log"
6
	"os"
7
	"path/filepath"
8
	"time"
9
10
	"github.com/prometheus/client_golang/prometheus"
11
)
12
13
var (
14
	// HTTP request metrics.
15
	HTTPRequestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
16
		Name: "goproxy_http_requests_total",
17
		Help: "Total HTTP requests by handler, method, and status.",
18
	}, []string{"handler", "method", "code"})
19
20
	HTTPRequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
21
		Name:    "goproxy_http_request_duration_seconds",
22
		Help:    "HTTP request duration in seconds.",
23
		Buckets: prometheus.DefBuckets,
24
	}, []string{"handler"})
25
26
	// Git cache metrics.
27
	GitReposTotal = prometheus.NewGauge(prometheus.GaugeOpts{
28
		Name: "goproxy_git_repos_total",
29
		Help: "Number of cached git repositories.",
30
	})
31
32
	GitCacheSizeBytes = prometheus.NewGauge(prometheus.GaugeOpts{
33
		Name: "goproxy_git_cache_size_bytes",
34
		Help: "Total disk size of git cache directory.",
35
	})
36
37
	GitOperationsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
38
		Name: "goproxy_git_operations_total",
39
		Help: "Total git operations by type (clone, fetch).",
40
	}, []string{"operation"})
41
42
	// S3 storage metrics.
43
	S3ObjectsTotal = prometheus.NewGauge(prometheus.GaugeOpts{
44
		Name: "goproxy_s3_objects_total",
45
		Help: "Number of objects in S3 bucket.",
46
	})
47
48
	S3SizeBytes = prometheus.NewGauge(prometheus.GaugeOpts{
49
		Name: "goproxy_s3_size_bytes",
50
		Help: "Total size of objects in S3 bucket.",
51
	})
52
53
	S3OperationsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
54
		Name: "goproxy_s3_operations_total",
55
		Help: "Total S3 operations by type and result.",
56
	}, []string{"operation", "result"})
57
58
	// Proxy cache metrics.
59
	ProxyCacheHitsTotal = prometheus.NewCounter(prometheus.CounterOpts{
60
		Name: "goproxy_cache_hits_total",
61
		Help: "Total S3 cache hits.",
62
	})
63
64
	ProxyCacheMissesTotal = prometheus.NewCounter(prometheus.CounterOpts{
65
		Name: "goproxy_cache_misses_total",
66
		Help: "Total S3 cache misses.",
67
	})
68
69
	// Module metrics.
70
	ModulesConfigured = prometheus.NewGauge(prometheus.GaugeOpts{
71
		Name: "goproxy_modules_configured",
72
		Help: "Number of modules in config.",
73
	})
74
75
	// Sum database metrics.
76
	SumdbRecordsTotal = prometheus.NewGauge(prometheus.GaugeOpts{
77
		Name: "goproxy_sumdb_records_total",
78
		Help: "Number of records in the sumdb transparency log.",
79
	})
80
81
	SumdbLookupsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
82
		Name: "goproxy_sumdb_lookups_total",
83
		Help: "Total sumdb lookups by result (hit, miss).",
84
	}, []string{"result"})
85
86
	SumdbTreeSize = prometheus.NewGauge(prometheus.GaugeOpts{
87
		Name: "goproxy_sumdb_tree_size",
88
		Help: "Current sumdb transparency log tree size.",
89
	})
90
91
	// Documentation metrics.
92
	DocRendersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
93
		Name: "goproxy_doc_renders_total",
94
		Help: "Total documentation page renders by type.",
95
	}, []string{"type"})
96
)
97
98
func Register() {
99
	prometheus.MustRegister(
100
		HTTPRequestsTotal,
101
		HTTPRequestDuration,
102
		GitReposTotal,
103
		GitCacheSizeBytes,
104
		GitOperationsTotal,
105
		S3ObjectsTotal,
106
		S3SizeBytes,
107
		S3OperationsTotal,
108
		ProxyCacheHitsTotal,
109
		ProxyCacheMissesTotal,
110
		ModulesConfigured,
111
		SumdbRecordsTotal,
112
		SumdbLookupsTotal,
113
		SumdbTreeSize,
114
		DocRendersTotal,
115
	)
116
}
117
118
// BucketStatsFunc returns object count and total size for S3 metrics collection.
119
type BucketStatsFunc func(ctx context.Context) (objects int64, sizeBytes int64, err error)
120
121
// RecordCountFunc returns the number of sumdb records.
122
type RecordCountFunc func() int64
123
124
// StartCollector periodically updates gauge metrics.
125
// ModuleCountFunc returns the number of configured modules.
126
type ModuleCountFunc func() int
127
128
func StartCollector(ctx context.Context, cacheDir string, moduleCount ModuleCountFunc, bucketStats BucketStatsFunc, recordCount RecordCountFunc, interval time.Duration) {
129
	collect := func() {
130
		if moduleCount != nil {
131
			ModulesConfigured.Set(float64(moduleCount()))
132
		}
133
134
		repos := countDirs(cacheDir)
135
		GitReposTotal.Set(float64(repos))
136
137
		size, err := dirSize(cacheDir)
138
		if err == nil {
139
			GitCacheSizeBytes.Set(float64(size))
140
		}
141
142
		if recordCount != nil {
143
			n := recordCount()
144
			SumdbRecordsTotal.Set(float64(n))
145
			SumdbTreeSize.Set(float64(n))
146
		}
147
148
		if bucketStats != nil {
149
			objects, bytes, err := bucketStats(context.Background())
150
			if err != nil {
151
				log.Printf("s3 stats: %v", err)
152
			} else {
153
				S3ObjectsTotal.Set(float64(objects))
154
				S3SizeBytes.Set(float64(bytes))
155
			}
156
		}
157
	}
158
159
	ticker := time.NewTicker(interval)
160
	go func() {
161
		collect()
162
163
		for {
164
			select {
165
			case <-ctx.Done():
166
				ticker.Stop()
167
				return
168
			case <-ticker.C:
169
				collect()
170
			}
171
		}
172
	}()
173
}
174
175
func dirSize(path string) (int64, error) {
176
	var size int64
177
	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
178
		if err != nil {
179
			return err
180
		}
181
		if !info.IsDir() {
182
			size += info.Size()
183
		}
184
		return nil
185
	})
186
	return size, err
187
}
188
189
func countDirs(path string) int {
190
	entries, err := os.ReadDir(path)
191
	if err != nil {
192
		return 0
193
	}
194
195
	count := 0
196
	for _, e := range entries {
197
		if e.IsDir() {
198
			count++
199
		}
200
	}
201
202
	return count
203
}
204

Source Files