162 lines
5.3 KiB
Go
162 lines
5.3 KiB
Go
// Copyright IBM Corp. 2020, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
// Package metric provides functions to initialize the controller specific
|
|
// collectors and hooks to measure metrics and update the relevant collectors.
|
|
package metric
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/reflect/protoregistry"
|
|
)
|
|
|
|
const (
|
|
LabelConnectionType = "conn_type"
|
|
LabelGrpcService = "grpc_service"
|
|
LabelGrpcMethod = "grpc_method"
|
|
LabelGrpcCode = "grpc_code"
|
|
LabelHttpPath = "path"
|
|
LabelHttpMethod = "method"
|
|
LabelHttpCode = "code"
|
|
|
|
invalidPathValue = "invalid"
|
|
)
|
|
|
|
var (
|
|
ListGrpcLabels = []string{LabelGrpcService, LabelGrpcMethod, LabelGrpcCode}
|
|
ListHttpLabels = []string{LabelHttpPath, LabelHttpMethod, LabelHttpCode}
|
|
)
|
|
|
|
/* The following methods are used to initialize Prometheus histogram vectors for gRPC connections. */
|
|
|
|
// rangeProtoFiles returns true while there are services with associated methods in the proto package.
|
|
// It relies on RangeFilesByPackage to range through the package, and it adds them into map m.
|
|
// Services and methods for which filter() returns true are not added into the map.
|
|
func rangeProtoFiles(m map[string][]string, fd protoreflect.FileDescriptor, filter func(string, string) bool) bool {
|
|
if fd.Services().Len() == 0 {
|
|
return true
|
|
}
|
|
|
|
for i := 0; i < fd.Services().Len(); i++ {
|
|
s := fd.Services().Get(i)
|
|
if s.Methods().Len() == 0 {
|
|
continue
|
|
}
|
|
|
|
serviceName := string(s.FullName())
|
|
methods := []string{}
|
|
for j := 0; j < s.Methods().Len(); j++ {
|
|
methodName := string(s.Methods().Get(j).Name())
|
|
if filter(serviceName, methodName) {
|
|
continue
|
|
}
|
|
methods = append(methods, methodName)
|
|
}
|
|
if len(methods) > 0 {
|
|
m[serviceName] = methods
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// appendServicesAndMethods ranges through all registered files in a specified proto package
|
|
// and appends service and method names to the provided map m.
|
|
func appendServicesAndMethods(m map[string][]string, pkg protoreflect.FileDescriptor, filter func(string, string) bool) {
|
|
protoregistry.GlobalFiles.RangeFilesByPackage(
|
|
pkg.Package(),
|
|
func(fd protoreflect.FileDescriptor) bool { return rangeProtoFiles(m, fd, filter) },
|
|
)
|
|
}
|
|
|
|
// InitializeGrpcCollectorsFromPackage registers and zeroes a Prometheus
|
|
// histogram, populating all service and method labels by ranging through
|
|
// the package containing the provided FileDescriptor.
|
|
// The filter function takes in a service name and method name and skips adding them as labels
|
|
// upon returning true.
|
|
// Note: inputting a protoreflect.FileDescriptor will populate all services and methods
|
|
// found in its package, not just methods associated with that specific FileDescriptor.
|
|
func InitializeGrpcCollectorsFromPackage(r prometheus.Registerer, v prometheus.ObserverVec,
|
|
pkgs []protoreflect.FileDescriptor, codes []codes.Code, filter func(string, string) bool,
|
|
) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.MustRegister(v)
|
|
|
|
serviceNamesToMethodNames := make(map[string][]string, 0)
|
|
for _, p := range pkgs {
|
|
appendServicesAndMethods(serviceNamesToMethodNames, p, filter)
|
|
}
|
|
|
|
for serviceName, serviceMethods := range serviceNamesToMethodNames {
|
|
for _, sm := range serviceMethods {
|
|
for _, c := range codes {
|
|
v.With(prometheus.Labels{LabelGrpcService: serviceName, LabelGrpcMethod: sm, LabelGrpcCode: c.String()})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func InitializeConnectionCounters(r prometheus.Registerer, counters []prometheus.CounterVec) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
for _, c := range counters {
|
|
r.MustRegister(c)
|
|
}
|
|
}
|
|
|
|
// InitializeGrpcCollectorsFromServer registers and zeroes a Prometheus
|
|
// histogram, finding all service and method labels from the provided gRPC
|
|
// server.
|
|
func InitializeGrpcCollectorsFromServer(r prometheus.Registerer, v prometheus.ObserverVec, server *grpc.Server, codes []codes.Code) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.MustRegister(v)
|
|
|
|
for serviceName, info := range server.GetServiceInfo() {
|
|
for _, mInfo := range info.Methods {
|
|
for _, c := range codes {
|
|
v.With(prometheus.Labels{LabelGrpcService: serviceName, LabelGrpcMethod: mInfo.Name, LabelGrpcCode: c.String()})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The following methods are used to initialize Prometheus histogram vectors for http requests. */
|
|
|
|
// InitializeApiCollectors registers and zeroes a Prometheus
|
|
// histogram, populating all path, code, and method labels from the
|
|
// provided maps in its parameters.
|
|
func InitializeApiCollectors(r prometheus.Registerer, v prometheus.ObserverVec, expectedPathsToMethods map[string][]string, expectedStatusCodesPerMethod map[string][]int) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.MustRegister(v)
|
|
|
|
for p, methods := range expectedPathsToMethods {
|
|
for _, m := range methods {
|
|
for _, sc := range expectedStatusCodesPerMethod[m] {
|
|
v.With(prometheus.Labels{LabelHttpPath: p, LabelHttpMethod: strings.ToLower(m), LabelHttpCode: strconv.Itoa(sc)})
|
|
}
|
|
}
|
|
}
|
|
|
|
// When an invalid path is found, any method is possible, but we expect an error response.
|
|
p := invalidPathValue
|
|
for m := range expectedStatusCodesPerMethod {
|
|
for _, sc := range []int{http.StatusNotFound, http.StatusMethodNotAllowed} {
|
|
v.With(prometheus.Labels{LabelHttpPath: p, LabelHttpMethod: strings.ToLower(m), LabelHttpCode: strconv.Itoa(sc)})
|
|
}
|
|
}
|
|
}
|