-
Notifications
You must be signed in to change notification settings - Fork 69
/
operation_metrics.go
128 lines (103 loc) · 4.63 KB
/
operation_metrics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package core
import (
"context"
"github.com/wundergraph/cosmo/router/pkg/otel"
"strconv"
"time"
"go.uber.org/zap"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/attribute"
)
type OperationProtocol string
const (
OperationProtocolHTTP = OperationProtocol("http")
OperationProtocolWS = OperationProtocol("ws")
)
func (p OperationProtocol) String() string {
return string(p)
}
// OperationMetrics is a struct that holds the metrics for an operation. It should be created on the parent router request
// subgraph metrics are created in the transport or engine loader hooks.
type OperationMetrics struct {
requestContentLength int64
routerMetrics RouterMetrics
operationStartTime time.Time
metricBaseFields []attribute.KeyValue
inflightMetric func()
routerConfigVersion string
opContext *operationContext
logger *zap.Logger
}
func (m *OperationMetrics) exportSchemaUsageInfo(operationContext *operationContext, statusCode int, hasError bool) {
m.routerMetrics.ExportSchemaUsageInfo(operationContext, statusCode, hasError)
}
func (m *OperationMetrics) AddOperationContext(opContext *operationContext) {
m.opContext = opContext
}
func (m *OperationMetrics) Finish(err error, statusCode int, responseSize int) {
m.inflightMetric()
ctx := context.Background()
rm := m.routerMetrics.MetricStore()
if err != nil {
// We don't store false values in the metrics, so only add the error attribute if it's true
m.metricBaseFields = append(m.metricBaseFields, otel.WgRequestError.Bool(true))
rm.MeasureRequestError(ctx, m.metricBaseFields...)
}
m.metricBaseFields = append(m.metricBaseFields, semconv.HTTPStatusCode(statusCode))
rm.MeasureRequestCount(ctx, m.metricBaseFields...)
rm.MeasureRequestSize(ctx, m.requestContentLength, m.metricBaseFields...)
rm.MeasureLatency(ctx,
m.operationStartTime,
m.metricBaseFields...,
)
rm.MeasureResponseSize(ctx, int64(responseSize), m.metricBaseFields...)
if m.opContext != nil {
m.exportSchemaUsageInfo(m.opContext, statusCode, err != nil)
}
}
func (m *OperationMetrics) AddAttributes(kv ...attribute.KeyValue) {
m.metricBaseFields = append(m.metricBaseFields, kv...)
}
// AddClientInfo adds the client info to the operation metrics. If OperationMetrics
// is nil, it's a no-op.
func (m *OperationMetrics) AddClientInfo(info *ClientInfo) {
if info == nil {
return
}
// Add client info to metrics base fields
m.metricBaseFields = append(m.metricBaseFields, otel.WgClientName.String(info.Name))
m.metricBaseFields = append(m.metricBaseFields, otel.WgClientVersion.String(info.Version))
}
// startOperationMetrics starts the metrics for an operation. This should only be called by
// routerMetrics.StartOperation()
func startOperationMetrics(rMetrics RouterMetrics, logger *zap.Logger, requestContentLength int64, routerConfigVersion string) *OperationMetrics {
operationStartTime := time.Now()
inflightMetric := rMetrics.MetricStore().MeasureInFlight(context.Background())
return &OperationMetrics{
requestContentLength: requestContentLength,
operationStartTime: operationStartTime,
inflightMetric: inflightMetric,
routerConfigVersion: routerConfigVersion,
routerMetrics: rMetrics,
logger: logger,
}
}
// setAttributesFromOperationContext returns the attributes that are common to both metrics and traces.
func setAttributesFromOperationContext(operationContext *operationContext) []attribute.KeyValue {
if operationContext == nil {
return nil
}
var baseMetricAttributeValues []attribute.KeyValue
// Fields that are always present in the metrics and traces
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgClientName.String(operationContext.clientInfo.Name))
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgClientVersion.String(operationContext.clientInfo.Version))
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgOperationName.String(operationContext.Name()))
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgOperationType.String(operationContext.Type()))
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgOperationProtocol.String(operationContext.Protocol().String()))
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgOperationHash.String(strconv.FormatUint(operationContext.Hash(), 10)))
// Common Field that will be present in both metrics and traces if not empty
if operationContext.PersistedID() != "" {
baseMetricAttributeValues = append(baseMetricAttributeValues, otel.WgOperationPersistedID.String(operationContext.PersistedID()))
}
return baseMetricAttributeValues
}