github.com/cilium/cilium@v1.16.2/pkg/hubble/metrics/http/handler.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Hubble
     3  
     4  package http
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strconv"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	flowpb "github.com/cilium/cilium/api/v1/flow"
    14  	"github.com/cilium/cilium/pkg/hubble/metrics/api"
    15  	"github.com/cilium/cilium/pkg/time"
    16  )
    17  
    18  type httpHandler struct {
    19  	requests  *prometheus.CounterVec
    20  	responses *prometheus.CounterVec
    21  	duration  *prometheus.HistogramVec
    22  	context   *api.ContextOptions
    23  	useV2     bool
    24  	exemplars bool
    25  
    26  	registeredMetrics []*prometheus.MetricVec
    27  }
    28  
    29  func (h *httpHandler) Init(registry *prometheus.Registry, options api.Options) error {
    30  	c, err := api.ParseContextOptions(options)
    31  	if err != nil {
    32  		return err
    33  	}
    34  	h.context = c
    35  	if exemplars, ok := options["exemplars"]; ok && exemplars == "true" {
    36  		h.exemplars = true
    37  	}
    38  
    39  	if h.useV2 {
    40  		h.requests = prometheus.NewCounterVec(prometheus.CounterOpts{
    41  			Namespace: api.DefaultPrometheusNamespace,
    42  			Name:      "http_requests_total",
    43  			Help:      "Count of HTTP requests",
    44  		}, append(h.context.GetLabelNames(), "method", "protocol", "status", "reporter"))
    45  		h.duration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
    46  			Namespace: api.DefaultPrometheusNamespace,
    47  			Name:      "http_request_duration_seconds",
    48  			Help:      "Quantiles of HTTP request duration in seconds",
    49  		}, append(h.context.GetLabelNames(), "method", "reporter"))
    50  		registry.MustRegister(h.requests)
    51  		registry.MustRegister(h.duration)
    52  		h.registeredMetrics = append(h.registeredMetrics, h.requests.MetricVec, h.duration.MetricVec)
    53  	} else {
    54  		h.requests = prometheus.NewCounterVec(prometheus.CounterOpts{
    55  			Namespace: api.DefaultPrometheusNamespace,
    56  			Name:      "http_requests_total",
    57  			Help:      "Count of HTTP requests",
    58  		}, append(h.context.GetLabelNames(), "method", "protocol", "reporter"))
    59  		h.responses = prometheus.NewCounterVec(prometheus.CounterOpts{
    60  			Namespace: api.DefaultPrometheusNamespace,
    61  			Name:      "http_responses_total",
    62  			Help:      "Count of HTTP responses",
    63  		}, append(h.context.GetLabelNames(), "method", "protocol", "status", "reporter"))
    64  		h.duration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
    65  			Namespace: api.DefaultPrometheusNamespace,
    66  			Name:      "http_request_duration_seconds",
    67  			Help:      "Quantiles of HTTP request duration in seconds",
    68  		}, append(h.context.GetLabelNames(), "method", "reporter"))
    69  		registry.MustRegister(h.requests)
    70  		registry.MustRegister(h.responses)
    71  		registry.MustRegister(h.duration)
    72  		h.registeredMetrics = append(h.registeredMetrics, h.requests.MetricVec, h.responses.MetricVec, h.duration.MetricVec)
    73  	}
    74  	return nil
    75  }
    76  
    77  func (h *httpHandler) Status() string {
    78  	if h.context == nil {
    79  		return ""
    80  	}
    81  	return h.context.Status() + fmt.Sprintf(",exemplars=%t", h.exemplars)
    82  }
    83  
    84  func (h *httpHandler) Context() *api.ContextOptions {
    85  	return h.context
    86  }
    87  
    88  func (h *httpHandler) ListMetricVec() []*prometheus.MetricVec {
    89  	return h.registeredMetrics
    90  }
    91  
    92  func (h *httpHandler) ProcessFlow(ctx context.Context, flow *flowpb.Flow) error {
    93  	if h.useV2 {
    94  		return h.processMetricsV2(flow)
    95  	} else {
    96  		return h.processMetricsV1(flow)
    97  	}
    98  }
    99  
   100  func (h *httpHandler) isHTTP(flow *flowpb.Flow) bool {
   101  	return flow.GetL7().GetHttp() != nil
   102  }
   103  
   104  func (h *httpHandler) reporter(flow *flowpb.Flow) string {
   105  	reporter := "unknown"
   106  	switch flow.GetTrafficDirection() {
   107  	case flowpb.TrafficDirection_EGRESS:
   108  		reporter = "client"
   109  	case flowpb.TrafficDirection_INGRESS:
   110  		reporter = "server"
   111  	}
   112  	return reporter
   113  }
   114  
   115  func (h *httpHandler) traceID(flow *flowpb.Flow) string {
   116  	if h.exemplars {
   117  		return flow.GetTraceContext().GetParent().GetTraceId()
   118  	}
   119  	return ""
   120  }
   121  
   122  func (h *httpHandler) processMetricsV2(flow *flowpb.Flow) error {
   123  	if !h.isHTTP(flow) || flow.GetL7().GetType() != flowpb.L7FlowType_RESPONSE {
   124  		return nil
   125  	}
   126  	reporter := h.reporter(flow)
   127  	traceID := h.traceID(flow)
   128  
   129  	labelValues, err := h.context.GetLabelValuesInvertSourceDestination(flow)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	http := flow.GetL7().GetHttp()
   135  	status := strconv.Itoa(int(http.GetCode()))
   136  	requestsCounter := h.requests.WithLabelValues(append(labelValues, http.GetMethod(), http.GetProtocol(), status, reporter)...)
   137  	requestDurationHistogram := h.duration.WithLabelValues(append(labelValues, http.GetMethod(), reporter)...)
   138  
   139  	incrementCounter(requestsCounter, traceID)
   140  	observerObserve(requestDurationHistogram, float64(flow.GetL7().GetLatencyNs())/float64(time.Second), traceID)
   141  
   142  	return nil
   143  }
   144  
   145  func (h *httpHandler) processMetricsV1(flow *flowpb.Flow) error {
   146  	if !h.isHTTP(flow) {
   147  		return nil
   148  	}
   149  	flowType := flow.GetL7().GetType()
   150  	if flowType != flowpb.L7FlowType_REQUEST && flowType != flowpb.L7FlowType_RESPONSE {
   151  		return nil
   152  	}
   153  	reporter := h.reporter(flow)
   154  	traceID := h.traceID(flow)
   155  
   156  	labelValues, err := h.context.GetLabelValues(flow)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	http := flow.GetL7().GetHttp()
   162  	var requestsCounter, responsesCounter prometheus.Counter
   163  	switch flow.GetL7().GetType() {
   164  	case flowpb.L7FlowType_REQUEST:
   165  		requestsCounter = h.requests.WithLabelValues(append(labelValues, http.GetMethod(), http.GetProtocol(), reporter)...)
   166  		incrementCounter(requestsCounter, traceID)
   167  	case flowpb.L7FlowType_RESPONSE:
   168  		status := strconv.Itoa(int(http.GetCode()))
   169  		responsesCounter = h.responses.WithLabelValues(append(labelValues, http.GetMethod(), http.GetProtocol(), status, reporter)...)
   170  		requestDurationHistogram := h.duration.WithLabelValues(append(labelValues, http.GetMethod(), reporter)...)
   171  		incrementCounter(responsesCounter, traceID)
   172  		observerObserve(requestDurationHistogram, float64(flow.GetL7().GetLatencyNs())/float64(time.Second), traceID)
   173  	}
   174  	return nil
   175  }
   176  
   177  func incrementCounter(c prometheus.Counter, traceID string) {
   178  	if adder, ok := c.(prometheus.ExemplarAdder); ok && traceID != "" {
   179  		adder.AddWithExemplar(1, prometheus.Labels{"traceID": traceID})
   180  	} else {
   181  		c.Inc()
   182  	}
   183  }
   184  
   185  func observerObserve(o prometheus.Observer, value float64, traceID string) {
   186  	if adder, ok := o.(prometheus.ExemplarObserver); ok && traceID != "" {
   187  		adder.ObserveWithExemplar(value, prometheus.Labels{"traceID": traceID})
   188  	} else {
   189  		o.Observe(value)
   190  	}
   191  }