...

Source file src/edge-infra.dev/pkg/edge/api/middleware/metrics.go

Documentation: edge-infra.dev/pkg/edge/api/middleware

     1  package middleware
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/99designs/gqlgen/graphql"
     9  	"github.com/gin-gonic/gin"
    10  	"github.com/penglongli/gin-metrics/ginmetrics"
    11  	"github.com/vektah/gqlparser/v2/ast"
    12  )
    13  
    14  const (
    15  	metricPath          = "/metrics"
    16  	maxLatencySeconds   = 1
    17  	graphQLRequests     = "graphql_request_total"
    18  	graphQlOperations   = "graphql_query_total"
    19  	graphQLSlowReQuests = "graphql_slow_request_total"
    20  	graphQLResponseTime = "graphql_query_duration"
    21  )
    22  
    23  func UseMetrics(r gin.IRoutes) {
    24  	// get global Monitor object
    25  	m := ginmetrics.GetMonitor()
    26  	m.SetMetricPath(metricPath)
    27  	m.SetSlowTime(maxLatencySeconds)
    28  
    29  	_ = m.AddMetric(&ginmetrics.Metric{
    30  		Type:        ginmetrics.Counter,
    31  		Name:        graphQLRequests,
    32  		Description: "count all graphql requests.",
    33  		Labels:      nil,
    34  	})
    35  
    36  	_ = m.AddMetric(&ginmetrics.Metric{
    37  		Type:        ginmetrics.Counter,
    38  		Name:        graphQlOperations,
    39  		Description: "count all graphql requests by name, operation and status.",
    40  		Labels:      []string{"name", "operation", "status"},
    41  	})
    42  
    43  	_ = m.AddMetric(&ginmetrics.Metric{
    44  		Type:        ginmetrics.Counter,
    45  		Name:        graphQLSlowReQuests,
    46  		Description: fmt.Sprintf("count all graphql slow requests by name, operation and status. max_lentency=%d sec.", maxLatencySeconds),
    47  		Labels:      []string{"name", "operation", "status"},
    48  	})
    49  
    50  	m.SetDuration([]float64{0.1, 0.3, 1.2, 5, 10})
    51  	_ = m.AddMetric(&ginmetrics.Metric{
    52  		Type:        ginmetrics.Histogram,
    53  		Name:        graphQLResponseTime,
    54  		Description: "the time graphql took handle the request by name.",
    55  		Labels:      []string{"name"},
    56  		// +required set request duration, default {0.1, 0.3, 1.2, 5, 10}
    57  		// used to p95, p99
    58  		Buckets: []float64{0.1, 0.3, 1.2, 5, 10},
    59  	})
    60  
    61  	m.Use(r)
    62  }
    63  
    64  func HandleGraphQlMetrics(ctx *graphql.OperationContext, resp *graphql.Response, start time.Time) {
    65  	latency := time.Since(start)
    66  
    67  	// metrics for any graphql query
    68  	_ = ginmetrics.GetMonitor().GetMetric(graphQLRequests).Inc(nil)
    69  
    70  	name := "no_selection"
    71  	selections := GetSelectionNames(ctx)
    72  	if len(selections) > 0 {
    73  		name = strings.Join(selections, ",")
    74  	}
    75  
    76  	operation := ctx.OperationName
    77  	if operation == "" && ctx != nil && ctx.Operation != nil {
    78  		operation = string(ctx.Operation.Operation)
    79  	}
    80  	if operation == "" {
    81  		operation = "unknown"
    82  	}
    83  
    84  	status := "success"
    85  	if resp != nil && len(resp.Errors) > 0 {
    86  		status = "error"
    87  	}
    88  
    89  	labels := []string{name, operation, status}
    90  
    91  	// metrics per name, operation and status
    92  	_ = ginmetrics.GetMonitor().GetMetric(graphQlOperations).Inc(labels)
    93  
    94  	// slow metrics per name, operation and status
    95  	if int32(latency.Seconds()) > maxLatencySeconds {
    96  		_ = ginmetrics.GetMonitor().GetMetric(graphQLSlowReQuests).Inc(labels)
    97  	}
    98  
    99  	// metrics for response time
   100  	_ = ginmetrics.GetMonitor().GetMetric(graphQLResponseTime).Observe([]string{name}, latency.Seconds())
   101  }
   102  
   103  // GetSelectionNames return selection names and ignore introspection queries
   104  func GetSelectionNames(rc *graphql.OperationContext) []string {
   105  	var selections []string
   106  	if rc.Operation != nil { // can be null if query/mutation is invalid
   107  		for _, selection := range rc.Operation.SelectionSet {
   108  			selectionName := selection.(*ast.Field).Name
   109  			// queries that starts with underscore are used for introspection (used by graphql playground)
   110  			if !strings.HasPrefix(selectionName, "_") {
   111  				selections = append(selections, selectionName)
   112  			}
   113  		}
   114  	}
   115  	return selections
   116  }
   117  

View as plain text