Files
zot/pkg/exporter/api/exporter.go
T
Andrei Aaron da426850e7 chore: update golangci-lint and fix all issues (#3575)
* chore: Update golangci-lint

Signed-off-by: Lars Francke <git@lars-francke.de>

* chore: fix all golangci-lint issues

- Remove deprecated `// +build` tags
- Fix godoclint, modernize, wsl_v5, govet, lll, gci, noctx issues
- Update linter configuration
- Modernize code to use Go 1.22+ features (for range N, slices.Contains, etc.)
- Update make check lint the privileged tests

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>

---------

Signed-off-by: Lars Francke <git@lars-francke.de>
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
Co-authored-by: Lars Francke <git@lars-francke.de>
2025-11-22 23:36:48 +02:00

195 lines
6.2 KiB
Go

//go:build !metrics
//nolint:varnamelen
package api
import (
"fmt"
"math"
"net/http"
"regexp"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"zotregistry.dev/zot/v2/pkg/extensions/monitoring"
"zotregistry.dev/zot/v2/pkg/log"
)
const (
idleTimeout = 120 * time.Second
readHeaderTimeout = 5 * time.Second
)
type Collector struct {
Client *monitoring.MetricsClient
MetricsDesc map[string]*prometheus.Desc // all known metrics descriptions
invalidChars *regexp.Regexp
}
// Describe implements prometheus.Collector interface.
func (zc Collector) Describe(ch chan<- *prometheus.Desc) {
for _, metricDescription := range zc.MetricsDesc {
ch <- metricDescription
}
}
// Collect implements prometheus.Collector interface.
func (zc Collector) Collect(ch chan<- prometheus.Metric) {
metrics, err := zc.Client.GetMetrics()
if err != nil {
fmt.Printf("error getting metrics: %v\n", err)
ch <- prometheus.MustNewConstMetric(zc.MetricsDesc["zot_up"], prometheus.GaugeValue, 0)
return
}
ch <- prometheus.MustNewConstMetric(zc.MetricsDesc["zot_up"], prometheus.GaugeValue, 1)
for _, g := range metrics.Gauges {
name := zc.invalidChars.ReplaceAllLiteralString(g.Name, "_")
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.GaugeValue, g.Value, g.LabelValues...)
}
for _, c := range metrics.Counters {
name := zc.invalidChars.ReplaceAllLiteralString(c.Name, "_")
name += "_total"
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.CounterValue, float64(c.Count), c.LabelValues...)
}
for _, summary := range metrics.Summaries {
mname := zc.invalidChars.ReplaceAllLiteralString(summary.Name, "_")
name := mname + "_count"
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.CounterValue, float64(summary.Count), summary.LabelValues...)
name = mname + "_sum"
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.CounterValue, summary.Sum, summary.LabelValues...)
}
for _, h := range metrics.Histograms {
mname := zc.invalidChars.ReplaceAllLiteralString(h.Name, "_")
name := mname + "_count"
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.CounterValue, float64(h.Count), h.LabelValues...)
name = mname + "_sum"
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.CounterValue, h.Sum, h.LabelValues...)
if h.Buckets != nil {
for _, fvalue := range monitoring.GetBuckets(h.Name) {
var svalue string
if fvalue == math.MaxFloat64 {
svalue = "+Inf"
} else {
svalue = strconv.FormatFloat(fvalue, 'f', -1, 64)
}
name = mname + "_bucket"
ch <- prometheus.MustNewConstMetric(
zc.MetricsDesc[name], prometheus.CounterValue, float64(h.Buckets[svalue]), append(h.LabelValues, svalue)...)
}
}
}
}
func panicOnDuplicateMetricName(m map[string]*prometheus.Desc, name string, log log.Logger) {
if _, present := m[name]; present {
log.Fatal().Str("metric", name).Msg("duplicate key found")
}
}
func GetCollector(c *Controller) *Collector {
// compute all metrics description map
MetricsDesc := map[string]*prometheus.Desc{
"zot_up": prometheus.NewDesc(
"zot_up",
"Connection to zot server was successfully established.",
nil, nil,
),
}
invalidChars := regexp.MustCompile("[^a-zA-Z0-9:_]")
for metricName, metricLabelNames := range monitoring.GetCounters() {
name := invalidChars.ReplaceAllLiteralString(metricName, "_")
name += "_total"
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
}
for metricName, metricLabelNames := range monitoring.GetGauges() {
name := invalidChars.ReplaceAllLiteralString(metricName, "_")
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
}
for metricName, metricLabelNames := range monitoring.GetSummaries() {
mname := invalidChars.ReplaceAllLiteralString(metricName, "_")
name := mname + "_count"
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
name = mname + "_sum"
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
}
for metricName, metricLabelNames := range monitoring.GetHistograms() {
mname := invalidChars.ReplaceAllLiteralString(metricName, "_")
name := mname + "_count"
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
name = mname + "_sum"
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
name = mname + "_bucket"
panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
// Append a new label to hitogram bucket - le - 'lower or equal'
MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, append(metricLabelNames, "le"), nil)
}
// parameters to connect to the zot server
serverAddr := fmt.Sprintf("%s://%s:%s", c.Config.Server.Protocol,
c.Config.Server.Host, c.Config.Server.Port)
cfg := &monitoring.MetricsConfig{Address: serverAddr}
return &Collector{
Client: monitoring.NewMetricsClient(cfg, c.Log),
MetricsDesc: MetricsDesc,
invalidChars: invalidChars,
}
}
func runExporter(c *Controller) {
exporterAddr := ":" + c.Config.Exporter.Port
server := &http.Server{
Addr: exporterAddr,
IdleTimeout: idleTimeout,
ReadHeaderTimeout: readHeaderTimeout,
}
err := prometheus.Register(GetCollector(c))
if err != nil {
c.Log.Debug().Err(err).Msg("ignoring error")
}
http.Handle(c.Config.Exporter.Metrics.Path, promhttp.Handler())
c.Log.Info().Str("addr", exporterAddr).
Str("path", c.Config.Exporter.Metrics.Path).
Msg("exporter listening")
serverAddr := fmt.Sprintf("%s://%s:%s", c.Config.Server.Protocol,
c.Config.Server.Host, c.Config.Server.Port)
c.Log.Info().Str("serverAddr", serverAddr).Msg("scraping metrics")
c.Log.Fatal().Err(server.ListenAndServe()).Msg("exporter stopped")
}