|
| 1 | +package network |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "strconv" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/prometheus/client_golang/prometheus" |
| 10 | + "go.opentelemetry.io/otel/attribute" |
| 11 | + "go.opentelemetry.io/otel/metric" |
| 12 | + sdkmetric "go.opentelemetry.io/otel/sdk/metric" |
| 13 | + |
| 14 | + "github.com/smartcontractkit/chainlink-common/pkg/beholder" |
| 15 | +) |
| 16 | + |
| 17 | +// TracePhase identifies a phase of the HTTP client request lifecycle as |
| 18 | +// observed via net/http/httptrace. |
| 19 | +type TracePhase string |
| 20 | + |
| 21 | +const ( |
| 22 | + PhaseGetConn TracePhase = "get_conn" |
| 23 | + PhaseDNSLookup TracePhase = "dns_lookup" |
| 24 | + PhaseTCPConnect TracePhase = "tcp_connect" |
| 25 | + PhaseTLSHandshake TracePhase = "tls_handshake" |
| 26 | + PhaseWroteRequest TracePhase = "wrote_request" |
| 27 | + PhaseTimeToFirstByte TracePhase = "time_to_first_byte" |
| 28 | + PhaseTotal TracePhase = "total" |
| 29 | +) |
| 30 | + |
| 31 | +type httpClientMetrics struct { |
| 32 | + phaseDuration metric.Int64Histogram |
| 33 | +} |
| 34 | + |
| 35 | +func newHTTPClientMetrics() (*httpClientMetrics, error) { |
| 36 | + phaseDuration, err := beholder.GetMeter().Int64Histogram( |
| 37 | + "platform_gateway_http_client_phase_duration_ms", |
| 38 | + metric.WithUnit("ms"), |
| 39 | + metric.WithDescription("HTTP client request phase duration observed via httptrace. The count of phase=total observations is the request count, partitioned by method, statusCode, success, and connectionReused."), |
| 40 | + ) |
| 41 | + if err != nil { |
| 42 | + return nil, fmt.Errorf("failed to create platform_gateway_http_client_phase_duration_ms histogram: %w", err) |
| 43 | + } |
| 44 | + |
| 45 | + return &httpClientMetrics{ |
| 46 | + phaseDuration: phaseDuration, |
| 47 | + }, nil |
| 48 | +} |
| 49 | + |
| 50 | +func (m *httpClientMetrics) recordPhase(ctx context.Context, method string, phase TracePhase, d time.Duration) { |
| 51 | + m.phaseDuration.Record(ctx, d.Milliseconds(), metric.WithAttributes( |
| 52 | + attribute.String("method", method), |
| 53 | + attribute.String("phase", string(phase)), |
| 54 | + )) |
| 55 | +} |
| 56 | + |
| 57 | +// recordTotal records the total request lifetime with the result attributes. |
| 58 | +// The histogram's count for phase=total doubles as the request counter. |
| 59 | +func (m *httpClientMetrics) recordTotal(ctx context.Context, method string, statusCode int, success, connReused bool, d time.Duration) { |
| 60 | + m.phaseDuration.Record(ctx, d.Milliseconds(), metric.WithAttributes( |
| 61 | + attribute.String("method", method), |
| 62 | + attribute.String("phase", string(PhaseTotal)), |
| 63 | + attribute.String("statusCode", strconv.Itoa(statusCode)), |
| 64 | + attribute.String("success", strconv.FormatBool(success)), |
| 65 | + attribute.String("connectionReused", strconv.FormatBool(connReused)), |
| 66 | + )) |
| 67 | +} |
| 68 | + |
| 69 | +// HTTPClientMetricViews returns histogram bucket definitions for the HTTP client trace metrics. |
| 70 | +// Due to the OTEL specification, all histogram buckets must be defined when the beholder client is created. |
| 71 | +func HTTPClientMetricViews() []sdkmetric.View { |
| 72 | + return []sdkmetric.View{ |
| 73 | + sdkmetric.NewView( |
| 74 | + sdkmetric.Instrument{Name: "platform_gateway_http_client_phase_duration_ms"}, |
| 75 | + sdkmetric.Stream{Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ |
| 76 | + // 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 (ms) |
| 77 | + Boundaries: prometheus.ExponentialBuckets(1, 2, 16), |
| 78 | + }}, |
| 79 | + ), |
| 80 | + } |
| 81 | +} |
0 commit comments