Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(otelhttp): generate New Server Metrics in otelhttp #6411

Merged
merged 25 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a77b9d0
feat(otelhttp): Added 1.26 metrics support for HTTP server instrument…
flc1125 Dec 9, 2024
c6da5fa
fix typos
flc1125 Dec 9, 2024
f62983d
backup
flc1125 Dec 9, 2024
35c0460
feat(otelhttp): generate New Server Metrics in `otelhttp`
flc1125 Dec 9, 2024
c2c6864
docs(CHANGELOG): update changelog
flc1125 Dec 9, 2024
cb5f43b
docs(CHANGELOG): update changelog
flc1125 Dec 9, 2024
39b2c89
fix lint
flc1125 Dec 9, 2024
33efd69
Merge branch 'main' into otelhttp-server
flc1125 Dec 10, 2024
faf914f
Merge remote-tracking branch 'upstream/main' into otelhttp-server
flc1125 Dec 11, 2024
3a75837
resolve(conflict): resolve merge conflicts
flc1125 Dec 12, 2024
16e399e
resolve(conflict): resolve merge conflicts
flc1125 Dec 12, 2024
016b257
resolve(conflict): resolve merge conflicts
flc1125 Dec 12, 2024
f9a2369
Merge branch 'main' into otelhttp-server
flc1125 Dec 12, 2024
64588ce
make a temporary backup
flc1125 Dec 12, 2024
31f87e8
test(otelhttp): optimize tests
flc1125 Dec 13, 2024
18b176f
Resolve conflict
flc1125 Dec 13, 2024
e1cf824
Fix lint and test
flc1125 Dec 13, 2024
405d1de
Merge remote-tracking branch 'upstream/main' into otelhttp-server
flc1125 Dec 13, 2024
b750320
optimize
flc1125 Dec 13, 2024
6a94cce
test(otelhttp): optimize tests
flc1125 Dec 13, 2024
1bf0f8d
test(otelhttp): optimize tests
flc1125 Dec 13, 2024
fb25ee4
Update CHANGELOG.md
flc1125 Dec 13, 2024
7c06fbc
Merge remote-tracking branch 'upstream/main' into otelhttp-server
flc1125 Dec 14, 2024
f887780
test(otelhttp): increase unit test coverage
flc1125 Dec 14, 2024
220d4a8
Merge branch 'main' into otelhttp-server
dmathieu Dec 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- Set the environment variable `OTEL_SEMCONV_STABILITY_OPT_IN` to `http/dup` to generate new server metric information based on the `semconv` 1.26 version in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6411)
flc1125 marked this conversation as resolved.
Show resolved Hide resolved

## [1.33.0/0.58.0/0.27.0/0.13.0/0.8.0/0.6.0/0.5.0] - 2024-12-12

### Added
Expand Down
69 changes: 48 additions & 21 deletions instrumentation/net/http/otelhttp/internal/semconv/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import (
"go.opentelemetry.io/otel/metric"
)

// OTelSemConvStabilityOptIn is an environment variable.
// That can be set to "old" or "http/dup" to opt into the new HTTP semantic conventions.
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"

type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
Expand All @@ -31,6 +35,11 @@ type HTTPServer struct {
requestBytesCounter metric.Int64Counter
responseBytesCounter metric.Int64Counter
serverLatencyMeasure metric.Float64Histogram

// New metrics
requestBodySizeHistogram metric.Int64Histogram
responseBodySizeHistogram metric.Int64Histogram
requestDurationHistogram metric.Float64Histogram
}

// RequestTraceAttrs returns trace attributes for an HTTP request received by a
Expand Down Expand Up @@ -103,38 +112,56 @@ type MetricData struct {
ElapsedTime float64
}

var metricAddOptionPool = &sync.Pool{
New: func() interface{} {
return &[]metric.AddOption{}
},
}
var (
metricAddOptionPool = &sync.Pool{
New: func() interface{} {
return &[]metric.AddOption{}
},
}

func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
if s.requestBytesCounter == nil || s.responseBytesCounter == nil || s.serverLatencyMeasure == nil {
// This will happen if an HTTPServer{} is used instead of NewHTTPServer.
return
metricRecordOptionPool = &sync.Pool{
New: func() interface{} {
return &[]metric.RecordOption{}
},
}
)

attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
addOpts := metricAddOptionPool.Get().(*[]metric.AddOption)
*addOpts = append(*addOpts, o)
s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...)
s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...)
s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o)
*addOpts = (*addOpts)[:0]
metricAddOptionPool.Put(addOpts)
func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
if s.requestBytesCounter != nil && s.responseBytesCounter != nil && s.serverLatencyMeasure != nil {
attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
addOpts := metricAddOptionPool.Get().(*[]metric.AddOption)
*addOpts = append(*addOpts, o)
s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...)
s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...)
s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o)
*addOpts = (*addOpts)[:0]
metricAddOptionPool.Put(addOpts)
}

// TODO: Duplicate Metrics
if s.duplicate && s.requestDurationHistogram != nil && s.requestBodySizeHistogram != nil && s.responseBodySizeHistogram != nil {
attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
s.requestBodySizeHistogram.Record(ctx, md.RequestSize, *recordOpts...)
s.responseBodySizeHistogram.Record(ctx, md.ResponseSize, *recordOpts...)
s.requestDurationHistogram.Record(ctx, md.ElapsedTime, o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
}

func NewHTTPServer(meter metric.Meter) HTTPServer {
env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN"))
env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
duplicate := env == "http/dup"
server := HTTPServer{
duplicate: duplicate,
}
server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter)
if duplicate {
server.requestBodySizeHistogram, server.responseBodySizeHistogram, server.requestDurationHistogram = CurrentHTTPServer{}.createMeasures(meter)
}
return server
}

Expand All @@ -148,7 +175,7 @@ type HTTPClient struct {
}

func NewHTTPClient(meter metric.Meter) HTTPClient {
env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN"))
env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
client := HTTPClient{
duplicate: env == "http/dup",
}
Expand Down
83 changes: 83 additions & 0 deletions instrumentation/net/http/otelhttp/internal/semconv/httpconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
"fmt"
"net/http"
"reflect"
"slices"
"strconv"
"strings"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0"
)

Expand Down Expand Up @@ -199,6 +202,86 @@
return semconvNew.HTTPRoute(route)
}

func (n CurrentHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Int64Histogram, metric.Float64Histogram) {
if meter == nil {
return noop.Int64Histogram{}, noop.Int64Histogram{}, noop.Float64Histogram{}
}

var err error
requestBodySizeHistogram, err := meter.Int64Histogram(
semconvNew.HTTPServerRequestBodySizeName,
metric.WithUnit(semconvNew.HTTPServerRequestBodySizeUnit),
metric.WithDescription(semconvNew.HTTPServerRequestBodySizeDescription),
)
handleErr(err)

responseBodySizeHistogram, err := meter.Int64Histogram(
semconvNew.HTTPServerResponseBodySizeName,
metric.WithUnit(semconvNew.HTTPServerResponseBodySizeUnit),
metric.WithDescription(semconvNew.HTTPServerResponseBodySizeDescription),
)
handleErr(err)
requestDurationHistogram, err := meter.Float64Histogram(
semconvNew.HTTPServerRequestDurationName,
metric.WithUnit(semconvNew.HTTPServerRequestDurationUnit),
metric.WithDescription(semconvNew.HTTPServerRequestDurationDescription),
)
handleErr(err)

return requestBodySizeHistogram, responseBodySizeHistogram, requestDurationHistogram
}

func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 3
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)

Check warning on line 239 in instrumentation/net/http/otelhttp/internal/semconv/httpconv.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/httpconv.go#L239

Added line #L239 was not covered by tests
flc1125 marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
num++
}

Check warning on line 250 in instrumentation/net/http/otelhttp/internal/semconv/httpconv.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/httpconv.go#L249-L250

Added lines #L249 - L250 were not covered by tests
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}

if statusCode > 0 {
num++
}

attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconvNew.ServerAddress(host))

if hostPort > 0 {
attributes = append(attributes, semconvNew.ServerPort(hostPort))
}

Check warning on line 271 in instrumentation/net/http/otelhttp/internal/semconv/httpconv.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/net/http/otelhttp/internal/semconv/httpconv.go#L270-L271

Added lines #L270 - L271 were not covered by tests
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}

if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}

type CurrentHTTPClient struct{}

// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
go.opentelemetry.io/otel v1.33.0
go.opentelemetry.io/otel/metric v1.33.0
go.opentelemetry.io/otel/sdk v1.33.0
go.opentelemetry.io/otel/sdk/metric v1.33.0
)
Expand All @@ -17,7 +18,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading
Loading