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 }