zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/exporter/api/exporter.go (about)

     1  //go:build !metrics
     2  // +build !metrics
     3  
     4  //nolint:varnamelen
     5  package api
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"net/http"
    11  	"regexp"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/prometheus/client_golang/prometheus/promhttp"
    17  
    18  	"zotregistry.io/zot/pkg/extensions/monitoring"
    19  	"zotregistry.io/zot/pkg/log"
    20  )
    21  
    22  const (
    23  	idleTimeout       = 120 * time.Second
    24  	readHeaderTimeout = 5 * time.Second
    25  )
    26  
    27  type Collector struct {
    28  	Client       *monitoring.MetricsClient
    29  	MetricsDesc  map[string]*prometheus.Desc // all known metrics descriptions
    30  	invalidChars *regexp.Regexp
    31  }
    32  
    33  // Implements prometheus.Collector interface.
    34  func (zc Collector) Describe(ch chan<- *prometheus.Desc) {
    35  	for _, metricDescription := range zc.MetricsDesc {
    36  		ch <- metricDescription
    37  	}
    38  }
    39  
    40  // Implements prometheus.Collector interface.
    41  func (zc Collector) Collect(ch chan<- prometheus.Metric) {
    42  	metrics, err := zc.Client.GetMetrics()
    43  	if err != nil {
    44  		fmt.Printf("error getting metrics: %v\n", err)
    45  		ch <- prometheus.MustNewConstMetric(zc.MetricsDesc["zot_up"], prometheus.GaugeValue, 0)
    46  
    47  		return
    48  	}
    49  	ch <- prometheus.MustNewConstMetric(zc.MetricsDesc["zot_up"], prometheus.GaugeValue, 1)
    50  
    51  	for _, g := range metrics.Gauges {
    52  		name := zc.invalidChars.ReplaceAllLiteralString(g.Name, "_")
    53  		ch <- prometheus.MustNewConstMetric(
    54  			zc.MetricsDesc[name], prometheus.GaugeValue, g.Value, g.LabelValues...)
    55  	}
    56  
    57  	for _, c := range metrics.Counters {
    58  		name := zc.invalidChars.ReplaceAllLiteralString(c.Name, "_")
    59  		name += "_total"
    60  		ch <- prometheus.MustNewConstMetric(
    61  			zc.MetricsDesc[name], prometheus.CounterValue, float64(c.Count), c.LabelValues...)
    62  	}
    63  
    64  	for _, summary := range metrics.Summaries {
    65  		mname := zc.invalidChars.ReplaceAllLiteralString(summary.Name, "_")
    66  		name := mname + "_count"
    67  		ch <- prometheus.MustNewConstMetric(
    68  			zc.MetricsDesc[name], prometheus.CounterValue, float64(summary.Count), summary.LabelValues...)
    69  
    70  		name = mname + "_sum"
    71  		ch <- prometheus.MustNewConstMetric(
    72  			zc.MetricsDesc[name], prometheus.CounterValue, summary.Sum, summary.LabelValues...)
    73  	}
    74  
    75  	for _, h := range metrics.Histograms {
    76  		mname := zc.invalidChars.ReplaceAllLiteralString(h.Name, "_")
    77  		name := mname + "_count"
    78  		ch <- prometheus.MustNewConstMetric(
    79  			zc.MetricsDesc[name], prometheus.CounterValue, float64(h.Count), h.LabelValues...)
    80  
    81  		name = mname + "_sum"
    82  		ch <- prometheus.MustNewConstMetric(
    83  			zc.MetricsDesc[name], prometheus.CounterValue, h.Sum, h.LabelValues...)
    84  
    85  		if h.Buckets != nil {
    86  			for _, fvalue := range monitoring.GetBuckets(h.Name) {
    87  				var svalue string
    88  				if fvalue == math.MaxFloat64 {
    89  					svalue = "+Inf"
    90  				} else {
    91  					svalue = strconv.FormatFloat(fvalue, 'f', -1, 64)
    92  				}
    93  
    94  				name = mname + "_bucket"
    95  				ch <- prometheus.MustNewConstMetric(
    96  					zc.MetricsDesc[name], prometheus.CounterValue, float64(h.Buckets[svalue]), append(h.LabelValues, svalue)...)
    97  			}
    98  		}
    99  	}
   100  }
   101  
   102  func panicOnDuplicateMetricName(m map[string]*prometheus.Desc, name string, log log.Logger) {
   103  	if _, present := m[name]; present {
   104  		log.Fatal().Msg("Duplicate keys: metric " + name + " already present")
   105  	}
   106  }
   107  
   108  func GetCollector(c *Controller) *Collector {
   109  	// compute all metrics description map
   110  	MetricsDesc := map[string]*prometheus.Desc{
   111  		"zot_up": prometheus.NewDesc(
   112  			"zot_up",
   113  			"Connection to zot server was successfully established.",
   114  			nil, nil,
   115  		),
   116  	}
   117  	invalidChars := regexp.MustCompile("[^a-zA-Z0-9:_]")
   118  
   119  	for metricName, metricLabelNames := range monitoring.GetCounters() {
   120  		name := invalidChars.ReplaceAllLiteralString(metricName, "_")
   121  		name += "_total"
   122  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   123  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
   124  	}
   125  
   126  	for metricName, metricLabelNames := range monitoring.GetGauges() {
   127  		name := invalidChars.ReplaceAllLiteralString(metricName, "_")
   128  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   129  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
   130  	}
   131  
   132  	for metricName, metricLabelNames := range monitoring.GetSummaries() {
   133  		mname := invalidChars.ReplaceAllLiteralString(metricName, "_")
   134  
   135  		name := mname + "_count"
   136  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   137  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
   138  
   139  		name = mname + "_sum"
   140  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   141  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
   142  	}
   143  
   144  	for metricName, metricLabelNames := range monitoring.GetHistograms() {
   145  		mname := invalidChars.ReplaceAllLiteralString(metricName, "_")
   146  
   147  		name := mname + "_count"
   148  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   149  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
   150  
   151  		name = mname + "_sum"
   152  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   153  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, metricLabelNames, nil)
   154  
   155  		name = mname + "_bucket"
   156  		panicOnDuplicateMetricName(MetricsDesc, name, c.Log)
   157  		// Append a new label to hitogram bucket - le - 'lower or equal'
   158  		MetricsDesc[name] = prometheus.NewDesc(name, "Metric "+name, append(metricLabelNames, "le"), nil)
   159  	}
   160  
   161  	// parameters to connect to the zot server
   162  	serverAddr := fmt.Sprintf("%s://%s:%s", c.Config.Server.Protocol,
   163  		c.Config.Server.Host, c.Config.Server.Port)
   164  	cfg := &monitoring.MetricsConfig{Address: serverAddr}
   165  
   166  	return &Collector{
   167  		Client:       monitoring.NewMetricsClient(cfg, c.Log),
   168  		MetricsDesc:  MetricsDesc,
   169  		invalidChars: invalidChars,
   170  	}
   171  }
   172  
   173  func runExporter(c *Controller) {
   174  	exporterAddr := fmt.Sprintf(":%s", c.Config.Exporter.Port)
   175  	server := &http.Server{
   176  		Addr:              exporterAddr,
   177  		IdleTimeout:       idleTimeout,
   178  		ReadHeaderTimeout: readHeaderTimeout,
   179  	}
   180  
   181  	err := prometheus.Register(GetCollector(c))
   182  	if err != nil {
   183  		c.Log.Error().Err(err).Msg("Expected error in testing")
   184  	}
   185  
   186  	http.Handle(c.Config.Exporter.Metrics.Path, promhttp.Handler())
   187  	c.Log.Info().Str("exporter addr", exporterAddr).
   188  		Str("exporter metrics path", c.Config.Exporter.Metrics.Path).
   189  		Msg("Exporter is listening on exporter addr & exposes metrics on exporter metrics path")
   190  
   191  	serverAddr := fmt.Sprintf("%s://%s:%s", c.Config.Server.Protocol,
   192  		c.Config.Server.Host, c.Config.Server.Port)
   193  	c.Log.Info().Str("serverAddr", serverAddr).Msg("Scraping metrics")
   194  	c.Log.Fatal().Err(server.ListenAndServe()).Msg("Exporter stopped")
   195  }