You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/daemon/metric/initialize_metrics.go

162 lines
5.3 KiB

// Copyright (c) HashiCorp, Inc.
// 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)})
}
}
}