[chore] Update usage of OTEL libraries (#2725)

* otel to 1.24
* prometheus exporter to 0.46
* bunotel to 1.1.17

Also:
* Use schemaless URL for metrics
* Add software version to tracing schema
This commit is contained in:
Daenney 2024-03-11 15:34:34 +01:00 committed by GitHub
parent 8e88ee8d9c
commit 5e871e81a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
126 changed files with 12940 additions and 2267 deletions

32
go.mod
View file

@ -57,17 +57,17 @@ require (
github.com/uptrace/bun v1.1.17 github.com/uptrace/bun v1.1.17
github.com/uptrace/bun/dialect/pgdialect v1.1.17 github.com/uptrace/bun/dialect/pgdialect v1.1.17
github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 github.com/uptrace/bun/dialect/sqlitedialect v1.1.17
github.com/uptrace/bun/extra/bunotel v1.1.16 github.com/uptrace/bun/extra/bunotel v1.1.17
github.com/wagslane/go-password-validator v0.3.0 github.com/wagslane/go-password-validator v0.3.0
github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark v1.7.0
go.opentelemetry.io/otel v1.20.0 go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
go.opentelemetry.io/otel/exporters/prometheus v0.43.0 go.opentelemetry.io/otel/exporters/prometheus v0.46.0
go.opentelemetry.io/otel/metric v1.20.0 go.opentelemetry.io/otel/metric v1.24.0
go.opentelemetry.io/otel/sdk v1.20.0 go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/sdk/metric v1.20.0 go.opentelemetry.io/otel/sdk/metric v1.24.0
go.opentelemetry.io/otel/trace v1.20.0 go.opentelemetry.io/otel/trace v1.24.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.21.0
golang.org/x/image v0.15.0 golang.org/x/image v0.15.0
@ -142,7 +142,7 @@ require (
github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/imdario/mergo v0.3.12 // indirect
@ -176,7 +176,7 @@ require (
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
@ -204,8 +204,8 @@ require (
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect go.mongodb.org/mongo-driver v1.11.3 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.7.0 // indirect
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
@ -214,9 +214,9 @@ require (
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/tools v0.17.0 // indirect golang.org/x/tools v0.17.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/grpc v1.59.0 // indirect google.golang.org/grpc v1.61.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

70
go.sum
View file

@ -312,8 +312,6 @@ github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgR
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -398,8 +396,8 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -551,8 +549,8 @@ github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
@ -681,8 +679,8 @@ github.com/uptrace/bun/dialect/pgdialect v1.1.17 h1:NsvFVHAx1Az6ytlAD/B6ty3cVE6j
github.com/uptrace/bun/dialect/pgdialect v1.1.17/go.mod h1:fLBDclNc7nKsZLzNjFL6BqSdgJzbj2HdnyOnLoDvAME= github.com/uptrace/bun/dialect/pgdialect v1.1.17/go.mod h1:fLBDclNc7nKsZLzNjFL6BqSdgJzbj2HdnyOnLoDvAME=
github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 h1:i8NFU9r8YuavNFaYlNqi4ppn+MgoHtqLgpWQDrVTjm0= github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 h1:i8NFU9r8YuavNFaYlNqi4ppn+MgoHtqLgpWQDrVTjm0=
github.com/uptrace/bun/dialect/sqlitedialect v1.1.17/go.mod h1:YF0FO4VVnY9GHNH6rM4r3STlVEBxkOc6L88Bm5X5mzA= github.com/uptrace/bun/dialect/sqlitedialect v1.1.17/go.mod h1:YF0FO4VVnY9GHNH6rM4r3STlVEBxkOc6L88Bm5X5mzA=
github.com/uptrace/bun/extra/bunotel v1.1.16 h1:qkLTaTZK3FZk3b2P/stO/krS7KX9Fq5wSOj7Hlb2HG8= github.com/uptrace/bun/extra/bunotel v1.1.17 h1:RLEJdHH06RI9BLg06Vu1JHJ3KNHQCfwa2Fa3x+56qkk=
github.com/uptrace/bun/extra/bunotel v1.1.16/go.mod h1:JwEH0kdXFnzYuK8D6eXUrf9HKsYy5wmB+lqQ/+dvH4E= github.com/uptrace/bun/extra/bunotel v1.1.17/go.mod h1:xV7AYrCFji4Sio6N9X+Cz+XJ+JuHq6TQQjuxaVbsypk=
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 h1:LNi0Qa7869/loPjz2kmMvp/jwZZnMZ9scMJKhDJ1DIo= github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 h1:LNi0Qa7869/loPjz2kmMvp/jwZZnMZ9scMJKhDJ1DIo=
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3/go.mod h1:jyigonKik3C5V895QNiAGpKYKEvFuqjw9qAEZks1mUg= github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3/go.mod h1:jyigonKik3C5V895QNiAGpKYKEvFuqjw9qAEZks1mUg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -736,26 +734,26 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0 h1:CsBiKCiQPdSjS+MlRiqeTI9JDDpSuk0Hb6QTRfwer8k= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0/go.mod h1:CMJYNAfooOwSZSAmAeMUV1M+TXld3BiK++z9fqIm2xk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/prometheus v0.43.0 h1:Skkl6akzvdWweXX6LLAY29tyFSO6hWZ26uDbVGTDXe8= go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ=
go.opentelemetry.io/otel/exporters/prometheus v0.43.0/go.mod h1:nZStMoc1H/YJpRjSx9IEX4abBMekORTLQcTUT1CgLkg= go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs=
go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/sdk/metric v1.20.0 h1:5eD40l/H2CqdKmbSV7iht2KMK0faAIL2pVYzJOWobGk= go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8=
go.opentelemetry.io/otel/sdk/metric v1.20.0/go.mod h1:AGvpC+YF/jblITiafMTYgvRBUiwi9hZf0EYE2E5XlS8= go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0=
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -1063,12 +1061,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -1081,8 +1079,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View file

@ -34,7 +34,7 @@
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
sdk "go.opentelemetry.io/otel/sdk/metric" sdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
const ( const (
@ -42,7 +42,6 @@
) )
func Initialize(db db.DB) error { func Initialize(db db.DB) error {
if !config.GetMetricsEnabled() { if !config.GetMetricsEnabled() {
return nil return nil
} }
@ -55,8 +54,7 @@ func Initialize(db db.DB) error {
r, _ := resource.Merge( r, _ := resource.Merge(
resource.Default(), resource.Default(),
resource.NewWithAttributes( resource.NewSchemaless(
semconv.SchemaURL,
semconv.ServiceName(serviceName), semconv.ServiceName(serviceName),
semconv.ServiceVersion(config.GetSoftwareVersion()), semconv.ServiceVersion(config.GetSoftwareVersion()),
), ),
@ -90,7 +88,6 @@ func Initialize(db db.DB) error {
return nil return nil
}), }),
) )
if err != nil { if err != nil {
return err return err
} }
@ -107,7 +104,6 @@ func Initialize(db db.DB) error {
return nil return nil
}), }),
) )
if err != nil { if err != nil {
return err return err
} }
@ -124,7 +120,6 @@ func Initialize(db db.DB) error {
return nil return nil
}), }),
) )
if err != nil { if err != nil {
return err return err
} }

View file

@ -35,7 +35,7 @@
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/semconv/v1.20.0/httpconv" "go.opentelemetry.io/otel/semconv/v1.20.0/httpconv"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
oteltrace "go.opentelemetry.io/otel/trace" oteltrace "go.opentelemetry.io/otel/trace"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
@ -88,6 +88,7 @@ func Initialize() error {
resource.Default(), resource.Default(),
resource.NewSchemaless( resource.NewSchemaless(
semconv.ServiceName("GoToSocial"), semconv.ServiceName("GoToSocial"),
semconv.ServiceVersion(config.GetSoftwareVersion()),
), ),
) )
if err != nil { if err != nil {
@ -164,7 +165,7 @@ func InstrumentGin() gin.HandlerFunc {
status := c.Writer.Status() status := c.Writer.Status()
span.SetStatus(httpconv.ServerStatus(status)) span.SetStatus(httpconv.ServerStatus(status))
if status > 0 { if status > 0 {
span.SetAttributes(semconv.HTTPStatusCode(status)) span.SetAttributes(semconv.HTTPResponseStatusCode(status))
} }
if len(c.Errors) > 0 { if len(c.Errors) > 0 {
span.SetAttributes(attribute.String("gin.errors", c.Errors.String())) span.SetAttributes(attribute.String("gin.errors", c.Errors.String()))

View file

@ -24,7 +24,7 @@ go_test(
embed = [":httprule"], embed = [":httprule"],
deps = [ deps = [
"//utilities", "//utilities",
"@com_github_golang_glog//:glog", "@org_golang_google_grpc//grpclog",
], ],
) )

View file

@ -26,7 +26,7 @@ go_library(
deps = [ deps = [
"//internal/httprule", "//internal/httprule",
"//utilities", "//utilities",
"@go_googleapis//google/api:httpbody_go_proto", "@org_golang_google_genproto_googleapis_api//httpbody",
"@org_golang_google_grpc//codes", "@org_golang_google_grpc//codes",
"@org_golang_google_grpc//grpclog", "@org_golang_google_grpc//grpclog",
"@org_golang_google_grpc//health/grpc_health_v1", "@org_golang_google_grpc//health/grpc_health_v1",
@ -70,9 +70,9 @@ go_test(
"//utilities", "//utilities",
"@com_github_google_go_cmp//cmp", "@com_github_google_go_cmp//cmp",
"@com_github_google_go_cmp//cmp/cmpopts", "@com_github_google_go_cmp//cmp/cmpopts",
"@go_googleapis//google/api:httpbody_go_proto", "@org_golang_google_genproto_googleapis_api//httpbody",
"@go_googleapis//google/rpc:errdetails_go_proto", "@org_golang_google_genproto_googleapis_rpc//errdetails",
"@go_googleapis//google/rpc:status_go_proto", "@org_golang_google_genproto_googleapis_rpc//status",
"@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes", "@org_golang_google_grpc//codes",
"@org_golang_google_grpc//health/grpc_health_v1", "@org_golang_google_grpc//health/grpc_health_v1",

View file

@ -137,7 +137,7 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
doForwardTrailers := requestAcceptsTrailers(r) doForwardTrailers := requestAcceptsTrailers(r)
if doForwardTrailers { if doForwardTrailers {
handleForwardResponseTrailerHeader(w, md) handleForwardResponseTrailerHeader(w, mux, md)
w.Header().Set("Transfer-Encoding", "chunked") w.Header().Set("Transfer-Encoding", "chunked")
} }
@ -152,7 +152,7 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
} }
if doForwardTrailers { if doForwardTrailers {
handleForwardResponseTrailer(w, md) handleForwardResponseTrailer(w, mux, md)
} }
} }

View file

@ -27,7 +27,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*field_mask.Field
var root interface{} var root interface{}
if err := json.NewDecoder(r).Decode(&root); err != nil { if err := json.NewDecoder(r).Decode(&root); err != nil {
if err == io.EOF { if errors.Is(err, io.EOF) {
return fm, nil return fm, nil
} }
return nil, err return nil, err

View file

@ -2,7 +2,7 @@
import ( import (
"context" "context"
"fmt" "errors"
"io" "io"
"net/http" "net/http"
"net/textproto" "net/textproto"
@ -48,7 +48,7 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
var wroteHeader bool var wroteHeader bool
for { for {
resp, err := recv() resp, err := recv()
if err == io.EOF { if errors.Is(err, io.EOF) {
return return
} }
if err != nil { if err != nil {
@ -108,18 +108,20 @@ func handleForwardResponseServerMetadata(w http.ResponseWriter, mux *ServeMux, m
} }
} }
func handleForwardResponseTrailerHeader(w http.ResponseWriter, md ServerMetadata) { func handleForwardResponseTrailerHeader(w http.ResponseWriter, mux *ServeMux, md ServerMetadata) {
for k := range md.TrailerMD { for k := range md.TrailerMD {
tKey := textproto.CanonicalMIMEHeaderKey(fmt.Sprintf("%s%s", MetadataTrailerPrefix, k)) if h, ok := mux.outgoingTrailerMatcher(k); ok {
w.Header().Add("Trailer", tKey) w.Header().Add("Trailer", textproto.CanonicalMIMEHeaderKey(h))
}
} }
} }
func handleForwardResponseTrailer(w http.ResponseWriter, md ServerMetadata) { func handleForwardResponseTrailer(w http.ResponseWriter, mux *ServeMux, md ServerMetadata) {
for k, vs := range md.TrailerMD { for k, vs := range md.TrailerMD {
tKey := fmt.Sprintf("%s%s", MetadataTrailerPrefix, k) if h, ok := mux.outgoingTrailerMatcher(k); ok {
for _, v := range vs { for _, v := range vs {
w.Header().Add(tKey, v) w.Header().Add(h, v)
}
} }
} }
} }
@ -147,12 +149,10 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
doForwardTrailers := requestAcceptsTrailers(req) doForwardTrailers := requestAcceptsTrailers(req)
if doForwardTrailers { if doForwardTrailers {
handleForwardResponseTrailerHeader(w, md) handleForwardResponseTrailerHeader(w, mux, md)
w.Header().Set("Transfer-Encoding", "chunked") w.Header().Set("Transfer-Encoding", "chunked")
} }
handleForwardResponseTrailerHeader(w, md)
contentType := marshaler.ContentType(resp) contentType := marshaler.ContentType(resp)
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
@ -178,7 +178,7 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
} }
if doForwardTrailers { if doForwardTrailers {
handleForwardResponseTrailer(w, md) handleForwardResponseTrailer(w, mux, md)
} }
} }

View file

@ -26,7 +26,7 @@ func (h *HTTPBodyMarshaler) ContentType(v interface{}) string {
// google.api.HttpBody message, otherwise it falls back to the default Marshaler. // google.api.HttpBody message, otherwise it falls back to the default Marshaler.
func (h *HTTPBodyMarshaler) Marshal(v interface{}) ([]byte, error) { func (h *HTTPBodyMarshaler) Marshal(v interface{}) ([]byte, error) {
if httpBody, ok := v.(*httpbody.HttpBody); ok { if httpBody, ok := v.(*httpbody.HttpBody); ok {
return httpBody.Data, nil return httpBody.GetData(), nil
} }
return h.Marshaler.Marshal(v) return h.Marshaler.Marshal(v)
} }

View file

@ -57,6 +57,7 @@ type ServeMux struct {
marshalers marshalerRegistry marshalers marshalerRegistry
incomingHeaderMatcher HeaderMatcherFunc incomingHeaderMatcher HeaderMatcherFunc
outgoingHeaderMatcher HeaderMatcherFunc outgoingHeaderMatcher HeaderMatcherFunc
outgoingTrailerMatcher HeaderMatcherFunc
metadataAnnotators []func(context.Context, *http.Request) metadata.MD metadataAnnotators []func(context.Context, *http.Request) metadata.MD
errorHandler ErrorHandlerFunc errorHandler ErrorHandlerFunc
streamErrorHandler StreamErrorHandlerFunc streamErrorHandler StreamErrorHandlerFunc
@ -114,10 +115,18 @@ func DefaultHeaderMatcher(key string) (string, bool) {
return "", false return "", false
} }
func defaultOutgoingHeaderMatcher(key string) (string, bool) {
return fmt.Sprintf("%s%s", MetadataHeaderPrefix, key), true
}
func defaultOutgoingTrailerMatcher(key string) (string, bool) {
return fmt.Sprintf("%s%s", MetadataTrailerPrefix, key), true
}
// WithIncomingHeaderMatcher returns a ServeMuxOption representing a headerMatcher for incoming request to gateway. // WithIncomingHeaderMatcher returns a ServeMuxOption representing a headerMatcher for incoming request to gateway.
// //
// This matcher will be called with each header in http.Request. If matcher returns true, that header will be // This matcher will be called with each header in http.Request. If matcher returns true, that header will be
// passed to gRPC context. To transform the header before passing to gRPC context, matcher should return modified header. // passed to gRPC context. To transform the header before passing to gRPC context, matcher should return the modified header.
func WithIncomingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption { func WithIncomingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption {
for _, header := range fn.matchedMalformedHeaders() { for _, header := range fn.matchedMalformedHeaders() {
grpclog.Warningf("The configured forwarding filter would allow %q to be sent to the gRPC server, which will likely cause errors. See https://github.com/grpc/grpc-go/pull/4803#issuecomment-986093310 for more information.", header) grpclog.Warningf("The configured forwarding filter would allow %q to be sent to the gRPC server, which will likely cause errors. See https://github.com/grpc/grpc-go/pull/4803#issuecomment-986093310 for more information.", header)
@ -147,13 +156,24 @@ func (fn HeaderMatcherFunc) matchedMalformedHeaders() []string {
// //
// This matcher will be called with each header in response header metadata. If matcher returns true, that header will be // This matcher will be called with each header in response header metadata. If matcher returns true, that header will be
// passed to http response returned from gateway. To transform the header before passing to response, // passed to http response returned from gateway. To transform the header before passing to response,
// matcher should return modified header. // matcher should return the modified header.
func WithOutgoingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption { func WithOutgoingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption {
return func(mux *ServeMux) { return func(mux *ServeMux) {
mux.outgoingHeaderMatcher = fn mux.outgoingHeaderMatcher = fn
} }
} }
// WithOutgoingTrailerMatcher returns a ServeMuxOption representing a headerMatcher for outgoing response from gateway.
//
// This matcher will be called with each header in response trailer metadata. If matcher returns true, that header will be
// passed to http response returned from gateway. To transform the header before passing to response,
// matcher should return the modified header.
func WithOutgoingTrailerMatcher(fn HeaderMatcherFunc) ServeMuxOption {
return func(mux *ServeMux) {
mux.outgoingTrailerMatcher = fn
}
}
// WithMetadata returns a ServeMuxOption for passing metadata to a gRPC context. // WithMetadata returns a ServeMuxOption for passing metadata to a gRPC context.
// //
// This can be used by services that need to read from http.Request and modify gRPC context. A common use case // This can be used by services that need to read from http.Request and modify gRPC context. A common use case
@ -273,11 +293,11 @@ func NewServeMux(opts ...ServeMuxOption) *ServeMux {
if serveMux.incomingHeaderMatcher == nil { if serveMux.incomingHeaderMatcher == nil {
serveMux.incomingHeaderMatcher = DefaultHeaderMatcher serveMux.incomingHeaderMatcher = DefaultHeaderMatcher
} }
if serveMux.outgoingHeaderMatcher == nil { if serveMux.outgoingHeaderMatcher == nil {
serveMux.outgoingHeaderMatcher = func(key string) (string, bool) { serveMux.outgoingHeaderMatcher = defaultOutgoingHeaderMatcher
return fmt.Sprintf("%s%s", MetadataHeaderPrefix, key), true }
} if serveMux.outgoingTrailerMatcher == nil {
serveMux.outgoingTrailerMatcher = defaultOutgoingTrailerMatcher
} }
return serveMux return serveMux

View file

@ -483,6 +483,8 @@ type Histogram struct {
// histograms. // histograms.
PositiveDelta []int64 `protobuf:"zigzag64,13,rep,name=positive_delta,json=positiveDelta" json:"positive_delta,omitempty"` // Count delta of each bucket compared to previous one (or to zero for 1st bucket). PositiveDelta []int64 `protobuf:"zigzag64,13,rep,name=positive_delta,json=positiveDelta" json:"positive_delta,omitempty"` // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
PositiveCount []float64 `protobuf:"fixed64,14,rep,name=positive_count,json=positiveCount" json:"positive_count,omitempty"` // Absolute count of each bucket. PositiveCount []float64 `protobuf:"fixed64,14,rep,name=positive_count,json=positiveCount" json:"positive_count,omitempty"` // Absolute count of each bucket.
// Only used for native histograms. These exemplars MUST have a timestamp.
Exemplars []*Exemplar `protobuf:"bytes,16,rep,name=exemplars" json:"exemplars,omitempty"`
} }
func (x *Histogram) Reset() { func (x *Histogram) Reset() {
@ -622,6 +624,13 @@ func (x *Histogram) GetPositiveCount() []float64 {
return nil return nil
} }
func (x *Histogram) GetExemplars() []*Exemplar {
if x != nil {
return x.Exemplars
}
return nil
}
// A Bucket of a conventional histogram, each of which is treated as // A Bucket of a conventional histogram, each of which is treated as
// an individual counter-like time series by Prometheus. // an individual counter-like time series by Prometheus.
type Bucket struct { type Bucket struct {
@ -923,6 +932,7 @@ type MetricFamily struct {
Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"` Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"` Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"` Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
Unit *string `protobuf:"bytes,5,opt,name=unit" json:"unit,omitempty"`
} }
func (x *MetricFamily) Reset() { func (x *MetricFamily) Reset() {
@ -985,6 +995,13 @@ func (x *MetricFamily) GetMetric() []*Metric {
return nil return nil
} }
func (x *MetricFamily) GetUnit() string {
if x != nil && x.Unit != nil {
return *x.Unit
}
return ""
}
var File_io_prometheus_client_metrics_proto protoreflect.FileDescriptor var File_io_prometheus_client_metrics_proto protoreflect.FileDescriptor
var file_io_prometheus_client_metrics_proto_rawDesc = []byte{ var file_io_prometheus_client_metrics_proto_rawDesc = []byte{
@ -1028,7 +1045,7 @@ func (x *MetricFamily) GetMetric() []*Metric {
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x1f, 0x0a, 0x07, 0x55, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x1f, 0x0a, 0x07, 0x55, 0x6e, 0x74,
0x79, 0x70, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x79, 0x70, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xac, 0x05, 0x0a, 0x09, 0x48, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xea, 0x05, 0x0a, 0x09, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70,
0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b,
0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73,
@ -1071,79 +1088,84 @@ func (x *MetricFamily) GetMetric() []*Metric {
0x03, 0x28, 0x12, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x44, 0x65, 0x6c, 0x03, 0x28, 0x12, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x44, 0x65, 0x6c,
0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x01, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x01, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69,
0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x42, 0x75, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x09, 0x65, 0x78, 0x65,
0x63, 0x6b, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69,
0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69,
0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x09, 0x65, 0x78,
0x34, 0x0a, 0x16, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x42, 0x75, 0x63, 0x6b,
0x75, 0x6e, 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65,
0x14, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x75,
0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a,
0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x70, 0x65, 0x16, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x14, 0x63,
0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6c,
0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x6f, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x6f, 0x75,
0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x70, 0x65, 0x72, 0x42,
0x61, 0x72, 0x22, 0x3c, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72,
0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d,
0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78,
0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72,
0x22, 0x91, 0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x12, 0x35, 0x0a, 0x22, 0x3c, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x16,
0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06,
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x91,
0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x12, 0x35, 0x0a, 0x05, 0x6c,
0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e,
0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x62,
0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x22, 0xff, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x35, 0x0a,
0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69,
0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x6c, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x6c,
0x61, 0x62, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x61, 0x75, 0x67, 0x65,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72,
0x74, 0x61, 0x6d, 0x70, 0x22, 0xff, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e,
0x35, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x12, 0x37, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75,
0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x18, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x07, 0x75, 0x6e, 0x74,
0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x61, 0x75, 0x79, 0x70, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e,
0x67, 0x65, 0x52, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e,
0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x74, 0x2e, 0x55, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x52, 0x07, 0x75, 0x6e, 0x74, 0x79, 0x70,
0x65, 0x72, 0x12, 0x37, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18,
0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65,
0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x69, 0x73,
0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x07, 0x75, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61,
0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d,
0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x65, 0x6e, 0x74, 0x2e, 0x55, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x52, 0x07, 0x75, 0x6e, 0x74, 0x6d, 0x70, 0x4d, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x46,
0x79, 0x70, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x6c,
0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12, 0x34, 0x0a,
0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x69, 0x6f,
0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65,
0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x22, 0xa2, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x04, 0x20,
0x63, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69,
0x65, 0x6c, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69,
0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x2a, 0x62, 0x0a,
0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x0a, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43,
0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x4d, 0x4d, 0x41, 0x52, 0x59, 0x10, 0x02,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x54, 0x59, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a,
0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x09, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f,
0x72, 0x69, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2a, 0x62, 0x0a, 0x0a, 0x4d, 0x47, 0x41, 0x55, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10,
0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, 0x05, 0x42, 0x52, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65,
0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75,
0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x4d, 0x4d, 0x41, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73,
0x0a, 0x07, 0x55, 0x4e, 0x54, 0x59, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x67, 0x6f,
0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41, 0x3b, 0x69, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x63,
0x55, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x05, 0x42, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x52, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73,
0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2f, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x67, 0x6f, 0x3b, 0x69,
0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74,
} }
var ( var (
@ -1185,22 +1207,23 @@ func file_io_prometheus_client_metrics_proto_rawDescGZIP() []byte {
13, // 5: io.prometheus.client.Histogram.created_timestamp:type_name -> google.protobuf.Timestamp 13, // 5: io.prometheus.client.Histogram.created_timestamp:type_name -> google.protobuf.Timestamp
9, // 6: io.prometheus.client.Histogram.negative_span:type_name -> io.prometheus.client.BucketSpan 9, // 6: io.prometheus.client.Histogram.negative_span:type_name -> io.prometheus.client.BucketSpan
9, // 7: io.prometheus.client.Histogram.positive_span:type_name -> io.prometheus.client.BucketSpan 9, // 7: io.prometheus.client.Histogram.positive_span:type_name -> io.prometheus.client.BucketSpan
10, // 8: io.prometheus.client.Bucket.exemplar:type_name -> io.prometheus.client.Exemplar 10, // 8: io.prometheus.client.Histogram.exemplars:type_name -> io.prometheus.client.Exemplar
1, // 9: io.prometheus.client.Exemplar.label:type_name -> io.prometheus.client.LabelPair 10, // 9: io.prometheus.client.Bucket.exemplar:type_name -> io.prometheus.client.Exemplar
13, // 10: io.prometheus.client.Exemplar.timestamp:type_name -> google.protobuf.Timestamp 1, // 10: io.prometheus.client.Exemplar.label:type_name -> io.prometheus.client.LabelPair
1, // 11: io.prometheus.client.Metric.label:type_name -> io.prometheus.client.LabelPair 13, // 11: io.prometheus.client.Exemplar.timestamp:type_name -> google.protobuf.Timestamp
2, // 12: io.prometheus.client.Metric.gauge:type_name -> io.prometheus.client.Gauge 1, // 12: io.prometheus.client.Metric.label:type_name -> io.prometheus.client.LabelPair
3, // 13: io.prometheus.client.Metric.counter:type_name -> io.prometheus.client.Counter 2, // 13: io.prometheus.client.Metric.gauge:type_name -> io.prometheus.client.Gauge
5, // 14: io.prometheus.client.Metric.summary:type_name -> io.prometheus.client.Summary 3, // 14: io.prometheus.client.Metric.counter:type_name -> io.prometheus.client.Counter
6, // 15: io.prometheus.client.Metric.untyped:type_name -> io.prometheus.client.Untyped 5, // 15: io.prometheus.client.Metric.summary:type_name -> io.prometheus.client.Summary
7, // 16: io.prometheus.client.Metric.histogram:type_name -> io.prometheus.client.Histogram 6, // 16: io.prometheus.client.Metric.untyped:type_name -> io.prometheus.client.Untyped
0, // 17: io.prometheus.client.MetricFamily.type:type_name -> io.prometheus.client.MetricType 7, // 17: io.prometheus.client.Metric.histogram:type_name -> io.prometheus.client.Histogram
11, // 18: io.prometheus.client.MetricFamily.metric:type_name -> io.prometheus.client.Metric 0, // 18: io.prometheus.client.MetricFamily.type:type_name -> io.prometheus.client.MetricType
19, // [19:19] is the sub-list for method output_type 11, // 19: io.prometheus.client.MetricFamily.metric:type_name -> io.prometheus.client.Metric
19, // [19:19] is the sub-list for method input_type 20, // [20:20] is the sub-list for method output_type
19, // [19:19] is the sub-list for extension type_name 20, // [20:20] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension extendee 20, // [20:20] is the sub-list for extension type_name
0, // [0:19] is the sub-list for field type_name 20, // [20:20] is the sub-list for extension extendee
0, // [0:20] is the sub-list for field type_name
} }
func init() { file_io_prometheus_client_metrics_proto_init() } func init() { file_io_prometheus_client_metrics_proto_init() }

View file

@ -3,3 +3,5 @@ fo
te te
collison collison
consequentially consequentially
ans
nam

View file

@ -14,13 +14,9 @@ go.work.sum
gen/ gen/
/example/dice/dice /example/dice/dice
/example/fib/fib
/example/fib/traces.txt
/example/jaeger/jaeger
/example/namedtracer/namedtracer /example/namedtracer/namedtracer
/example/otel-collector/otel-collector /example/otel-collector/otel-collector
/example/opencensus/opencensus /example/opencensus/opencensus
/example/passthrough/passthrough /example/passthrough/passthrough
/example/prometheus/prometheus /example/prometheus/prometheus
/example/view/view
/example/zipkin/zipkin /example/zipkin/zipkin

View file

@ -8,6 +8,134 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased] ## [Unreleased]
## [1.24.0/0.46.0/0.0.1-alpha] 2024-02-23
This release is the last to support [Go 1.20].
The next release will require at least [Go 1.21].
### Added
- Support [Go 1.22]. (#4890)
- Add exemplar support to `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#4900)
- Add exemplar support to `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4900)
- The `go.opentelemetry.io/otel/log` module is added.
This module includes OpenTelemetry Go's implementation of the Logs Bridge API.
This module is in an alpha state, it is subject to breaking changes.
See our [versioning policy](./VERSIONING.md) for more info. (#4961)
### Fixed
- Fix registration of multiple callbacks when using the global meter provider from `go.opentelemetry.io/otel`. (#4945)
- Fix negative buckets in output of exponential histograms. (#4956)
## [1.23.1] 2024-02-07
### Fixed
- Register all callbacks passed during observable instrument creation instead of just the last one multiple times in `go.opentelemetry.io/otel/sdk/metric`. (#4888)
## [1.23.0] 2024-02-06
This release contains the first stable, `v1`, release of the following modules:
- `go.opentelemetry.io/otel/bridge/opencensus`
- `go.opentelemetry.io/otel/bridge/opencensus/test`
- `go.opentelemetry.io/otel/example/opencensus`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`
- `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric`
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees.
### Added
- Add `WithEndpointURL` option to the `exporters/otlp/otlpmetric/otlpmetricgrpc`, `exporters/otlp/otlpmetric/otlpmetrichttp`, `exporters/otlp/otlptrace/otlptracegrpc` and `exporters/otlp/otlptrace/otlptracehttp` packages. (#4808)
- Experimental exemplar exporting is added to the metric SDK.
See [metric documentation](./sdk/metric/internal/x/README.md#exemplars) for more information about this feature and how to enable it. (#4871)
- `ErrSchemaURLConflict` is added to `go.opentelemetry.io/otel/sdk/resource`.
This error is returned when a merge of two `Resource`s with different (non-empty) schema URL is attempted. (#4876)
### Changed
- The `Merge` and `New` functions in `go.opentelemetry.io/otel/sdk/resource` now returns a partial result if there is a schema URL merge conflict.
Instead of returning `nil` when two `Resource`s with different (non-empty) schema URLs are merged the merged `Resource`, along with the new `ErrSchemaURLConflict` error, is returned.
It is up to the user to decide if they want to use the returned `Resource` or not.
It may have desired attributes overwritten or include stale semantic conventions. (#4876)
### Fixed
- Fix `ContainerID` resource detection on systemd when cgroup path has a colon. (#4449)
- Fix `go.opentelemetry.io/otel/sdk/metric` to cache instruments to avoid leaking memory when the same instrument is created multiple times. (#4820)
- Fix missing `Mix` and `Max` values for `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` by introducing `MarshalText` and `MarshalJSON` for the `Extrema` type in `go.opentelemetry.io/sdk/metric/metricdata`. (#4827)
## [1.23.0-rc.1] 2024-01-18
This is a release candidate for the v1.23.0 release.
That release is expected to include the `v1` release of the following modules:
- `go.opentelemetry.io/otel/bridge/opencensus`
- `go.opentelemetry.io/otel/bridge/opencensus/test`
- `go.opentelemetry.io/otel/example/opencensus`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`
- `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric`
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees.
## [1.22.0/0.45.0] 2024-01-17
### Added
- The `go.opentelemetry.io/otel/semconv/v1.22.0` package.
The package contains semantic conventions from the `v1.22.0` version of the OpenTelemetry Semantic Conventions. (#4735)
- The `go.opentelemetry.io/otel/semconv/v1.23.0` package.
The package contains semantic conventions from the `v1.23.0` version of the OpenTelemetry Semantic Conventions. (#4746)
- The `go.opentelemetry.io/otel/semconv/v1.23.1` package.
The package contains semantic conventions from the `v1.23.1` version of the OpenTelemetry Semantic Conventions. (#4749)
- The `go.opentelemetry.io/otel/semconv/v1.24.0` package.
The package contains semantic conventions from the `v1.24.0` version of the OpenTelemetry Semantic Conventions. (#4770)
- Add `WithResourceAsConstantLabels` option to apply resource attributes for every metric emitted by the Prometheus exporter. (#4733)
- Experimental cardinality limiting is added to the metric SDK.
See [metric documentation](./sdk/metric/internal/x/README.md#cardinality-limit) for more information about this feature and how to enable it. (#4457)
- Add `NewMemberRaw` and `NewKeyValuePropertyRaw` in `go.opentelemetry.io/otel/baggage`. (#4804)
### Changed
- Upgrade all use of `go.opentelemetry.io/otel/semconv` to use `v1.24.0`. (#4754)
- Update transformations in `go.opentelemetry.io/otel/exporters/zipkin` to follow `v1.24.0` version of the OpenTelemetry specification. (#4754)
- Record synchronous measurements when the passed context is canceled instead of dropping in `go.opentelemetry.io/otel/sdk/metric`.
If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g `if ctx.Err() != nil`). (#4671)
- Improve `go.opentelemetry.io/otel/trace.TraceState`'s performance. (#4722)
- Improve `go.opentelemetry.io/otel/propagation.TraceContext`'s performance. (#4721)
- Improve `go.opentelemetry.io/otel/baggage` performance. (#4743)
- Improve performance of the `(*Set).Filter` method in `go.opentelemetry.io/otel/attribute` when the passed filter does not filter out any attributes from the set. (#4774)
- `Member.String` in `go.opentelemetry.io/otel/baggage` percent-encodes only when necessary. (#4775)
- Improve `go.opentelemetry.io/otel/trace.Span`'s performance when adding multiple attributes. (#4818)
- `Property.Value` in `go.opentelemetry.io/otel/baggage` now returns a raw string instead of a percent-encoded value. (#4804)
### Fixed
- Fix `Parse` in `go.opentelemetry.io/otel/baggage` to validate member value before percent-decoding. (#4755)
- Fix whitespace encoding of `Member.String` in `go.opentelemetry.io/otel/baggage`. (#4756)
- Fix observable not registered error when the asynchronous instrument has a drop aggregation in `go.opentelemetry.io/otel/sdk/metric`. (#4772)
- Fix baggage item key so that it is not canonicalized in `go.opentelemetry.io/otel/bridge/opentracing`. (#4776)
- Fix `go.opentelemetry.io/otel/bridge/opentracing` to properly handle baggage values that requires escaping during propagation. (#4804)
- Fix a bug where using multiple readers resulted in incorrect asynchronous counter values in `go.opentelemetry.io/otel/sdk/metric`. (#4742)
## [1.21.0/0.44.0] 2023-11-16
### Removed
- Remove the deprecated `go.opentelemetry.io/otel/bridge/opencensus.NewTracer`. (#4706)
- Remove the deprecated `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` module. (#4707)
- Remove the deprecated `go.opentelemetry.io/otel/example/view` module. (#4708)
- Remove the deprecated `go.opentelemetry.io/otel/example/fib` module. (#4723)
### Fixed
- Do not parse non-protobuf responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4719)
- Do not parse non-protobuf responses in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#4719)
## [1.20.0/0.43.0] 2023-11-10 ## [1.20.0/0.43.0] 2023-11-10
This release brings a breaking change for custom trace API implementations. Some interfaces (`TracerProvider`, `Tracer`, `Span`) now embed the `go.opentelemetry.io/otel/trace/embedded` types. Implementors need to update their implementations based on what they want the default behavior to be. See the "API Implementations" section of the [trace API] package documentation for more information about how to accomplish this. This release brings a breaking change for custom trace API implementations. Some interfaces (`TracerProvider`, `Tracer`, `Span`) now embed the `go.opentelemetry.io/otel/trace/embedded` types. Implementors need to update their implementations based on what they want the default behavior to be. See the "API Implementations" section of the [trace API] package documentation for more information about how to accomplish this.
@ -2721,7 +2849,13 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files. - CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project. - CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.20.0...HEAD [Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.24.0...HEAD
[1.24.0/0.46.0/0.0.1-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.24.0
[1.23.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.1
[1.23.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0
[1.23.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0-rc.1
[1.22.0/0.45.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.22.0
[1.21.0/0.44.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.21.0
[1.20.0/0.43.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.20.0 [1.20.0/0.43.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.20.0
[1.19.0/0.42.0/0.0.7]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0 [1.19.0/0.42.0/0.0.7]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0
[1.19.0-rc.1/0.42.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0-rc.1 [1.19.0-rc.1/0.42.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0-rc.1
@ -2794,6 +2928,8 @@ It contains api and sdk for trace and meter.
[0.1.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.1 [0.1.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.1
[0.1.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.0 [0.1.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.0
[Go 1.22]: https://go.dev/doc/go1.22
[Go 1.21]: https://go.dev/doc/go1.21
[Go 1.20]: https://go.dev/doc/go1.20 [Go 1.20]: https://go.dev/doc/go1.20
[Go 1.19]: https://go.dev/doc/go1.19 [Go 1.19]: https://go.dev/doc/go1.19
[Go 1.18]: https://go.dev/doc/go1.18 [Go 1.18]: https://go.dev/doc/go1.18

View file

@ -14,4 +14,4 @@
* @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu * @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu
CODEOWNERS @MrAlias @MadVikingGod @pellared CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole

View file

@ -90,6 +90,10 @@ git push <YOUR_FORK> <YOUR_BRANCH_NAME>
Open a pull request against the main `opentelemetry-go` repo. Be sure to add the pull Open a pull request against the main `opentelemetry-go` repo. Be sure to add the pull
request ID to the entry you added to `CHANGELOG.md`. request ID to the entry you added to `CHANGELOG.md`.
Avoid rebasing and force-pushing to your branch to facilitate reviewing the pull request.
Rewriting Git history makes it difficult to keep track of iterations during code review.
All pull requests are squashed to a single commit upon merge to `main`.
### How to Receive Comments ### How to Receive Comments
* If the PR is not ready for review, please put `[WIP]` in the title, * If the PR is not ready for review, please put `[WIP]` in the title,
@ -587,25 +591,46 @@ this.
[^3]: https://github.com/open-telemetry/opentelemetry-go/issues/3548 [^3]: https://github.com/open-telemetry/opentelemetry-go/issues/3548
### Ignoring context cancellation
OpenTelemetry API implementations need to ignore the cancellation of the context that are
passed when recording a value (e.g. starting a span, recording a measurement, emitting a log).
Recording methods should not return an error describing the cancellation state of the context
when they complete, nor should they abort any work.
This rule may not apply if the OpenTelemetry specification defines a timeout mechanism for
the method. In that case the context cancellation can be used for the timeout with the
restriction that this behavior is documented for the method. Otherwise, timeouts
are expected to be handled by the user calling the API, not the implementation.
Stoppage of the telemetry pipeline is handled by calling the appropriate `Shutdown` method
of a provider. It is assumed the context passed from a user is not used for this purpose.
Outside of the direct recording of telemetry from the API (e.g. exporting telemetry,
force flushing telemetry, shutting down a signal provider) the context cancellation
should be honored. This means all work done on behalf of the user provided context
should be canceled.
## Approvers and Maintainers ## Approvers and Maintainers
### Approvers ### Approvers
- [Evan Torrie](https://github.com/evantorrie), Verizon Media - [Evan Torrie](https://github.com/evantorrie), Verizon Media
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics - [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
- [David Ashpole](https://github.com/dashpole), Google
- [Chester Cheung](https://github.com/hanyuancheung), Tencent - [Chester Cheung](https://github.com/hanyuancheung), Tencent
- [Damien Mathieu](https://github.com/dmathieu), Elastic - [Damien Mathieu](https://github.com/dmathieu), Elastic
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS - [Anthony Mirabella](https://github.com/Aneurysm9), AWS
### Maintainers ### Maintainers
- [David Ashpole](https://github.com/dashpole), Google
- [Aaron Clawson](https://github.com/MadVikingGod), LightStep - [Aaron Clawson](https://github.com/MadVikingGod), LightStep
- [Robert Pająk](https://github.com/pellared), Splunk - [Robert Pająk](https://github.com/pellared), Splunk
- [Tyler Yahn](https://github.com/MrAlias), Splunk - [Tyler Yahn](https://github.com/MrAlias), Splunk
### Emeritus ### Emeritus
- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb
- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep - [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep
- [Josh MacDonald](https://github.com/jmacd), LightStep - [Josh MacDonald](https://github.com/jmacd), LightStep

View file

@ -192,7 +192,7 @@ test-coverage: | $(GOCOVMERGE)
done; \ done; \
$(GOCOVMERGE) $$(find . -name coverage.out) > coverage.txt $(GOCOVMERGE) $$(find . -name coverage.out) > coverage.txt
# Adding a directory will include all benchmarks in that direcotry if a filter is not specified. # Adding a directory will include all benchmarks in that directory if a filter is not specified.
BENCHMARK_TARGETS := sdk/trace BENCHMARK_TARGETS := sdk/trace
.PHONY: benchmark .PHONY: benchmark
benchmark: $(BENCHMARK_TARGETS:%=benchmark/%) benchmark: $(BENCHMARK_TARGETS:%=benchmark/%)
@ -315,4 +315,4 @@ add-tags: | $(MULTIMOD)
.PHONY: lint-markdown .PHONY: lint-markdown
lint-markdown: lint-markdown:
docker run -v "$(CURDIR):$(WORKDIR)" docker://avtodev/markdown-lint:v1 -c $(WORKDIR)/.markdownlint.yaml $(WORKDIR)/**/*.md docker run -v "$(CURDIR):$(WORKDIR)" avtodev/markdown-lint:v1 -c $(WORKDIR)/.markdownlint.yaml $(WORKDIR)/**/*.md

View file

@ -11,14 +11,11 @@ It provides a set of APIs to directly measure performance and behavior of your s
## Project Status ## Project Status
| Signal | Status | | Signal | Status |
|---------|------------| |---------|--------------------|
| Traces | Stable | | Traces | Stable |
| Metrics | Stable | | Metrics | Stable |
| Logs | Design [1] | | Logs | In development[^1] |
- [1]: Currently the logs signal development is in a design phase ([#4696](https://github.com/open-telemetry/opentelemetry-go/issues/4696)).
No Logs Pull Requests are currently being accepted.
Progress and status specific to this repository is tracked in our Progress and status specific to this repository is tracked in our
[project boards](https://github.com/open-telemetry/opentelemetry-go/projects) [project boards](https://github.com/open-telemetry/opentelemetry-go/projects)
@ -28,6 +25,8 @@ and
Project versioning information and stability guarantees can be found in the Project versioning information and stability guarantees can be found in the
[versioning documentation](VERSIONING.md). [versioning documentation](VERSIONING.md).
[^1]: https://github.com/orgs/open-telemetry/projects/43
### Compatibility ### Compatibility
OpenTelemetry-Go ensures compatibility with the current supported versions of OpenTelemetry-Go ensures compatibility with the current supported versions of
@ -50,14 +49,19 @@ Currently, this project supports the following environments.
| OS | Go Version | Architecture | | OS | Go Version | Architecture |
|---------|------------|--------------| |---------|------------|--------------|
| Ubuntu | 1.22 | amd64 |
| Ubuntu | 1.21 | amd64 | | Ubuntu | 1.21 | amd64 |
| Ubuntu | 1.20 | amd64 | | Ubuntu | 1.20 | amd64 |
| Ubuntu | 1.22 | 386 |
| Ubuntu | 1.21 | 386 | | Ubuntu | 1.21 | 386 |
| Ubuntu | 1.20 | 386 | | Ubuntu | 1.20 | 386 |
| MacOS | 1.22 | amd64 |
| MacOS | 1.21 | amd64 | | MacOS | 1.21 | amd64 |
| MacOS | 1.20 | amd64 | | MacOS | 1.20 | amd64 |
| Windows | 1.22 | amd64 |
| Windows | 1.21 | amd64 | | Windows | 1.21 | amd64 |
| Windows | 1.20 | amd64 | | Windows | 1.20 | amd64 |
| Windows | 1.22 | 386 |
| Windows | 1.21 | 386 | | Windows | 1.21 | 386 |
| Windows | 1.20 | 386 | | Windows | 1.20 | 386 |
@ -66,7 +70,7 @@ are made for those systems currently.
## Getting Started ## Getting Started
You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/go/getting-started/). You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/languages/go/getting-started/).
OpenTelemetry's goal is to provide a single set of APIs to capture distributed OpenTelemetry's goal is to provide a single set of APIs to capture distributed
traces and metrics from your application and send them to an observability traces and metrics from your application and send them to an observability

View file

@ -123,12 +123,12 @@ Once verified be sure to [make a release for the `contrib` repository](https://g
### Website Documentation ### Website Documentation
Update the [Go instrumentation documentation] in the OpenTelemetry website under [content/en/docs/instrumentation/go]. Update the [Go instrumentation documentation] in the OpenTelemetry website under [content/en/docs/languages/go].
Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate. Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate.
[OpenTelemetry Semantic Conventions]: https://github.com/open-telemetry/semantic-conventions [OpenTelemetry Semantic Conventions]: https://github.com/open-telemetry/semantic-conventions
[Go instrumentation documentation]: https://opentelemetry.io/docs/instrumentation/go/ [Go instrumentation documentation]: https://opentelemetry.io/docs/languages/go/
[content/en/docs/instrumentation/go]: https://github.com/open-telemetry/opentelemetry.io/tree/main/content/en/docs/instrumentation/go [content/en/docs/languages/go]: https://github.com/open-telemetry/opentelemetry.io/tree/main/content/en/docs/languages/go
### Demo Repository ### Demo Repository

View file

@ -279,52 +279,75 @@ func NewSetWithSortableFiltered(kvs []KeyValue, tmp *Sortable, filter Filter) (S
position-- position--
kvs[offset], kvs[position] = kvs[position], kvs[offset] kvs[offset], kvs[position] = kvs[position], kvs[offset]
} }
kvs = kvs[position:]
if filter != nil { if filter != nil {
return filterSet(kvs[position:], filter) if div := filteredToFront(kvs, filter); div != 0 {
} return Set{equivalent: computeDistinct(kvs[div:])}, kvs[:div]
return Set{
equivalent: computeDistinct(kvs[position:]),
}, nil
}
// filterSet reorders kvs so that included keys are contiguous at the end of
// the slice, while excluded keys precede the included keys.
func filterSet(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
var excluded []KeyValue
// Move attributes that do not match the filter so they're adjacent before
// calling computeDistinct().
distinctPosition := len(kvs)
// Swap indistinct keys forward and distinct keys toward the
// end of the slice.
offset := len(kvs) - 1
for ; offset >= 0; offset-- {
if filter(kvs[offset]) {
distinctPosition--
kvs[offset], kvs[distinctPosition] = kvs[distinctPosition], kvs[offset]
continue
} }
} }
excluded = kvs[:distinctPosition] return Set{equivalent: computeDistinct(kvs)}, nil
}
return Set{ // filteredToFront filters slice in-place using keep function. All KeyValues that need to
equivalent: computeDistinct(kvs[distinctPosition:]), // be removed are moved to the front. All KeyValues that need to be kept are
}, excluded // moved (in-order) to the back. The index for the first KeyValue to be kept is
// returned.
func filteredToFront(slice []KeyValue, keep Filter) int {
n := len(slice)
j := n
for i := n - 1; i >= 0; i-- {
if keep(slice[i]) {
j--
slice[i], slice[j] = slice[j], slice[i]
}
}
return j
} }
// Filter returns a filtered copy of this Set. See the documentation for // Filter returns a filtered copy of this Set. See the documentation for
// NewSetWithSortableFiltered for more details. // NewSetWithSortableFiltered for more details.
func (l *Set) Filter(re Filter) (Set, []KeyValue) { func (l *Set) Filter(re Filter) (Set, []KeyValue) {
if re == nil { if re == nil {
return Set{ return *l, nil
equivalent: l.equivalent,
}, nil
} }
// Note: This could be refactored to avoid the temporary slice // Iterate in reverse to the first attribute that will be filtered out.
// allocation, if it proves to be expensive. n := l.Len()
return filterSet(l.ToSlice(), re) first := n - 1
for ; first >= 0; first-- {
kv, _ := l.Get(first)
if !re(kv) {
break
}
}
// No attributes will be dropped, return the immutable Set l and nil.
if first < 0 {
return *l, nil
}
// Copy now that we know we need to return a modified set.
//
// Do not do this in-place on the underlying storage of *Set l. Sets are
// immutable and filtering should not change this.
slice := l.ToSlice()
// Don't re-iterate the slice if only slice[0] is filtered.
if first == 0 {
// It is safe to assume len(slice) >= 1 given we found at least one
// attribute above that needs to be filtered out.
return Set{equivalent: computeDistinct(slice[1:])}, slice[:1]
}
// Move the filtered slice[first] to the front (preserving order).
kv := slice[first]
copy(slice[1:first+1], slice[:first])
slice[0] = kv
// Do not re-evaluate re(slice[first+1:]).
div := filteredToFront(slice[1:first+1], re) + 1
return Set{equivalent: computeDistinct(slice[div:])}, slice[:div]
} }
// computeDistinct returns a Distinct using either the fixed- or // computeDistinct returns a Distinct using either the fixed- or
@ -404,7 +427,7 @@ func (l *Set) MarshalJSON() ([]byte, error) {
return json.Marshal(l.equivalent.iface) return json.Marshal(l.equivalent.iface)
} }
// MarshalLog is the marshaling function used by the logging system to represent this exporter. // MarshalLog is the marshaling function used by the logging system to represent this Set.
func (l Set) MarshalLog() interface{} { func (l Set) MarshalLog() interface{} {
kvs := make(map[string]string) kvs := make(map[string]string)
for _, kv := range l.ToSlice() { for _, kv := range l.ToSlice() {

View file

@ -18,7 +18,6 @@
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"regexp"
"strings" "strings"
"go.opentelemetry.io/otel/internal/baggage" "go.opentelemetry.io/otel/internal/baggage"
@ -32,16 +31,6 @@
listDelimiter = "," listDelimiter = ","
keyValueDelimiter = "=" keyValueDelimiter = "="
propertyDelimiter = ";" propertyDelimiter = ";"
keyDef = `([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+)`
valueDef = `([\x21\x23-\x2b\x2d-\x3a\x3c-\x5B\x5D-\x7e]*)`
keyValueDef = `\s*` + keyDef + `\s*` + keyValueDelimiter + `\s*` + valueDef + `\s*`
)
var (
keyRe = regexp.MustCompile(`^` + keyDef + `$`)
valueRe = regexp.MustCompile(`^` + valueDef + `$`)
propertyRe = regexp.MustCompile(`^(?:\s*` + keyDef + `\s*|` + keyValueDef + `)$`)
) )
var ( var (
@ -67,7 +56,7 @@ type Property struct {
// //
// If key is invalid, an error will be returned. // If key is invalid, an error will be returned.
func NewKeyProperty(key string) (Property, error) { func NewKeyProperty(key string) (Property, error) {
if !keyRe.MatchString(key) { if !validateKey(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
} }
@ -77,14 +66,29 @@ func NewKeyProperty(key string) (Property, error) {
// NewKeyValueProperty returns a new Property for key with value. // NewKeyValueProperty returns a new Property for key with value.
// //
// If key or value are invalid, an error will be returned. // The passed key must be compliant with W3C Baggage specification.
// The passed value must be precent-encoded as defined in W3C Baggage specification.
//
// Notice: Consider using [NewKeyValuePropertyRaw] instead
// that does not require precent-encoding of the value.
func NewKeyValueProperty(key, value string) (Property, error) { func NewKeyValueProperty(key, value string) (Property, error) {
if !keyRe.MatchString(key) { if !validateValue(value) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
if !valueRe.MatchString(value) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value) return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
} }
decodedValue, err := url.PathUnescape(value)
if err != nil {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
return NewKeyValuePropertyRaw(key, decodedValue)
}
// NewKeyValuePropertyRaw returns a new Property for key with value.
//
// The passed key must be compliant with W3C Baggage specification.
func NewKeyValuePropertyRaw(key, value string) (Property, error) {
if !validateKey(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
p := Property{ p := Property{
key: key, key: key,
@ -106,20 +110,11 @@ func parseProperty(property string) (Property, error) {
return newInvalidProperty(), nil return newInvalidProperty(), nil
} }
match := propertyRe.FindStringSubmatch(property) p, ok := parsePropertyInternal(property)
if len(match) != 4 { if !ok {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property) return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property)
} }
var p Property
if match[1] != "" {
p.key = match[1]
} else {
p.key = match[2]
p.value = match[3]
p.hasValue = true
}
return p, nil return p, nil
} }
@ -130,12 +125,9 @@ func (p Property) validate() error {
return fmt.Errorf("invalid property: %w", err) return fmt.Errorf("invalid property: %w", err)
} }
if !keyRe.MatchString(p.key) { if !validateKey(p.key) {
return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key)) return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
} }
if p.hasValue && !valueRe.MatchString(p.value) {
return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
}
if !p.hasValue && p.value != "" { if !p.hasValue && p.value != "" {
return errFunc(errors.New("inconsistent value")) return errFunc(errors.New("inconsistent value"))
} }
@ -154,11 +146,11 @@ func (p Property) Value() (string, bool) {
return p.value, p.hasValue return p.value, p.hasValue
} }
// String encodes Property into a string compliant with the W3C Baggage // String encodes Property into a header string compliant with the W3C Baggage
// specification. // specification.
func (p Property) String() string { func (p Property) String() string {
if p.hasValue { if p.hasValue {
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value) return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value))
} }
return p.key return p.key
} }
@ -218,7 +210,7 @@ func (p properties) validate() error {
return nil return nil
} }
// String encodes properties into a string compliant with the W3C Baggage // String encodes properties into a header string compliant with the W3C Baggage
// specification. // specification.
func (p properties) String() string { func (p properties) String() string {
props := make([]string, len(p)) props := make([]string, len(p))
@ -240,11 +232,28 @@ type Member struct {
hasData bool hasData bool
} }
// NewMember returns a new Member from the passed arguments. The key will be // NewMemberRaw returns a new Member from the passed arguments.
// used directly while the value will be url decoded after validation. An error //
// is returned if the created Member would be invalid according to the W3C // The passed key must be compliant with W3C Baggage specification.
// Baggage specification. // The passed value must be precent-encoded as defined in W3C Baggage specification.
//
// Notice: Consider using [NewMemberRaw] instead
// that does not require precent-encoding of the value.
func NewMember(key, value string, props ...Property) (Member, error) { func NewMember(key, value string, props ...Property) (Member, error) {
if !validateValue(value) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
decodedValue, err := url.PathUnescape(value)
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
return NewMemberRaw(key, decodedValue, props...)
}
// NewMemberRaw returns a new Member from the passed arguments.
//
// The passed key must be compliant with W3C Baggage specification.
func NewMemberRaw(key, value string, props ...Property) (Member, error) {
m := Member{ m := Member{
key: key, key: key,
value: value, value: value,
@ -254,11 +263,6 @@ func NewMember(key, value string, props ...Property) (Member, error) {
if err := m.validate(); err != nil { if err := m.validate(); err != nil {
return newInvalidMember(), err return newInvalidMember(), err
} }
decodedValue, err := url.PathUnescape(value)
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
m.value = decodedValue
return m, nil return m, nil
} }
@ -274,11 +278,7 @@ func parseMember(member string) (Member, error) {
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n) return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
} }
var ( var props properties
key, value string
props properties
)
keyValue, properties, found := strings.Cut(member, propertyDelimiter) keyValue, properties, found := strings.Cut(member, propertyDelimiter)
if found { if found {
// Parse the member properties. // Parse the member properties.
@ -299,36 +299,34 @@ func parseMember(member string) (Member, error) {
} }
// "Leading and trailing whitespaces are allowed but MUST be trimmed // "Leading and trailing whitespaces are allowed but MUST be trimmed
// when converting the header into a data structure." // when converting the header into a data structure."
key = strings.TrimSpace(k) key := strings.TrimSpace(k)
var err error if !validateKey(key) {
value, err = url.PathUnescape(strings.TrimSpace(v))
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
}
if !keyRe.MatchString(key) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
} }
if !valueRe.MatchString(value) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) val := strings.TrimSpace(v)
if !validateValue(val) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, v)
} }
// Decode a precent-encoded value.
value, err := url.PathUnescape(val)
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %v", errInvalidValue, err)
}
return Member{key: key, value: value, properties: props, hasData: true}, nil return Member{key: key, value: value, properties: props, hasData: true}, nil
} }
// validate ensures m conforms to the W3C Baggage specification. // validate ensures m conforms to the W3C Baggage specification.
// A key is just an ASCII string, but a value must be URL encoded UTF-8, // A key must be an ASCII string, returning an error otherwise.
// returning an error otherwise.
func (m Member) validate() error { func (m Member) validate() error {
if !m.hasData { if !m.hasData {
return fmt.Errorf("%w: %q", errInvalidMember, m) return fmt.Errorf("%w: %q", errInvalidMember, m)
} }
if !keyRe.MatchString(m.key) { if !validateKey(m.key) {
return fmt.Errorf("%w: %q", errInvalidKey, m.key) return fmt.Errorf("%w: %q", errInvalidKey, m.key)
} }
if !valueRe.MatchString(m.value) {
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
}
return m.properties.validate() return m.properties.validate()
} }
@ -341,11 +339,13 @@ func (m Member) Value() string { return m.value }
// Properties returns a copy of the Member properties. // Properties returns a copy of the Member properties.
func (m Member) Properties() []Property { return m.properties.Copy() } func (m Member) Properties() []Property { return m.properties.Copy() }
// String encodes Member into a string compliant with the W3C Baggage // String encodes Member into a header string compliant with the W3C Baggage
// specification. // specification.
func (m Member) String() string { func (m Member) String() string {
// A key is just an ASCII string, but a value is URL encoded UTF-8. // A key is just an ASCII string. A value is restricted to be
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value)) // US-ASCII characters excluding CTLs, whitespace,
// DQUOTE, comma, semicolon, and backslash.
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, valueEscape(m.value))
if len(m.properties) > 0 { if len(m.properties) > 0 {
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String()) s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
} }
@ -536,9 +536,8 @@ func (b Baggage) Len() int {
return len(b.list) return len(b.list)
} }
// String encodes Baggage into a string compliant with the W3C Baggage // String encodes Baggage into a header string compliant with the W3C Baggage
// specification. The returned string will be invalid if the Baggage contains // specification.
// any invalid list-members.
func (b Baggage) String() string { func (b Baggage) String() string {
members := make([]string, 0, len(b.list)) members := make([]string, 0, len(b.list))
for k, v := range b.list { for k, v := range b.list {
@ -550,3 +549,196 @@ func (b Baggage) String() string {
} }
return strings.Join(members, listDelimiter) return strings.Join(members, listDelimiter)
} }
// parsePropertyInternal attempts to decode a Property from the passed string.
// It follows the spec at https://www.w3.org/TR/baggage/#definition.
func parsePropertyInternal(s string) (p Property, ok bool) {
// For the entire function we will use " key = value " as an example.
// Attempting to parse the key.
// First skip spaces at the beginning "< >key = value " (they could be empty).
index := skipSpace(s, 0)
// Parse the key: " <key> = value ".
keyStart := index
keyEnd := index
for _, c := range s[keyStart:] {
if !validateKeyChar(c) {
break
}
keyEnd++
}
// If we couldn't find any valid key character,
// it means the key is either empty or invalid.
if keyStart == keyEnd {
return
}
// Skip spaces after the key: " key< >= value ".
index = skipSpace(s, keyEnd)
if index == len(s) {
// A key can have no value, like: " key ".
ok = true
p.key = s[keyStart:keyEnd]
return
}
// If we have not reached the end and we can't find the '=' delimiter,
// it means the property is invalid.
if s[index] != keyValueDelimiter[0] {
return
}
// Attempting to parse the value.
// Match: " key =< >value ".
index = skipSpace(s, index+1)
// Match the value string: " key = <value> ".
// A valid property can be: " key =".
// Therefore, we don't have to check if the value is empty.
valueStart := index
valueEnd := index
for _, c := range s[valueStart:] {
if !validateValueChar(c) {
break
}
valueEnd++
}
// Skip all trailing whitespaces: " key = value< >".
index = skipSpace(s, valueEnd)
// If after looking for the value and skipping whitespaces
// we have not reached the end, it means the property is
// invalid, something like: " key = value value1".
if index != len(s) {
return
}
// Decode a precent-encoded value.
value, err := url.PathUnescape(s[valueStart:valueEnd])
if err != nil {
return
}
ok = true
p.key = s[keyStart:keyEnd]
p.hasValue = true
p.value = value
return
}
func skipSpace(s string, offset int) int {
i := offset
for ; i < len(s); i++ {
c := s[i]
if c != ' ' && c != '\t' {
break
}
}
return i
}
func validateKey(s string) bool {
if len(s) == 0 {
return false
}
for _, c := range s {
if !validateKeyChar(c) {
return false
}
}
return true
}
func validateKeyChar(c int32) bool {
return (c >= 0x23 && c <= 0x27) ||
(c >= 0x30 && c <= 0x39) ||
(c >= 0x41 && c <= 0x5a) ||
(c >= 0x5e && c <= 0x7a) ||
c == 0x21 ||
c == 0x2a ||
c == 0x2b ||
c == 0x2d ||
c == 0x2e ||
c == 0x7c ||
c == 0x7e
}
func validateValue(s string) bool {
for _, c := range s {
if !validateValueChar(c) {
return false
}
}
return true
}
func validateValueChar(c int32) bool {
return c == 0x21 ||
(c >= 0x23 && c <= 0x2b) ||
(c >= 0x2d && c <= 0x3a) ||
(c >= 0x3c && c <= 0x5b) ||
(c >= 0x5d && c <= 0x7e)
}
// valueEscape escapes the string so it can be safely placed inside a baggage value,
// replacing special characters with %XX sequences as needed.
//
// The implementation is based on:
// https://github.com/golang/go/blob/f6509cf5cdbb5787061b784973782933c47f1782/src/net/url/url.go#L285.
func valueEscape(s string) string {
hexCount := 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c) {
hexCount++
}
}
if hexCount == 0 {
return s
}
var buf [64]byte
var t []byte
required := len(s) + 2*hexCount
if required <= len(buf) {
t = buf[:required]
} else {
t = make([]byte, required)
}
j := 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(s[i]) {
const upperhex = "0123456789ABCDEF"
t[j] = '%'
t[j+1] = upperhex[c>>4]
t[j+2] = upperhex[c&15]
j += 3
} else {
t[j] = c
j++
}
}
return string(t)
}
// shouldEscape returns true if the specified byte should be escaped when
// appearing in a baggage value string.
func shouldEscape(c byte) bool {
if c == '%' {
// The percent character must be encoded so that percent-encoding can work.
return true
}
return !validateValueChar(int32(c))
}

View file

@ -22,7 +22,7 @@
default SDK implementation (go.opentelemetry.io/otel/sdk), and associated default SDK implementation (go.opentelemetry.io/otel/sdk), and associated
exporters are used to process and transport this data. exporters are used to process and transport this data.
To read the getting started guide, see https://opentelemetry.io/docs/go/getting-started/. To read the getting started guide, see https://opentelemetry.io/docs/languages/go/getting-started/.
To read more about tracing, see go.opentelemetry.io/otel/trace. To read more about tracing, see go.opentelemetry.io/otel/trace.

View file

@ -104,7 +104,7 @@ func NewUnstarted(client Client) *Exporter {
} }
} }
// MarshalLog is the marshaling function used by the logging system to represent this exporter. // MarshalLog is the marshaling function used by the logging system to represent this Exporter.
func (e *Exporter) MarshalLog() interface{} { func (e *Exporter) MarshalLog() interface{} {
return struct { return struct {
Type string Type string

View file

@ -28,7 +28,7 @@
The value accepts "http" and "https" scheme. The value accepts "http" and "https" scheme.
The value should not contain a query string or fragment. The value should not contain a query string or fragment.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT.
The configuration can be overridden by [WithEndpoint], [WithInsecure], [WithGRPCConn] options. The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options.
OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_TRACES_INSECURE (default: "false") - OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_TRACES_INSECURE (default: "false") -
setting "true" disables client transport security for the exporter's gRPC connection. setting "true" disables client transport security for the exporter's gRPC connection.

View file

@ -20,6 +20,7 @@
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/url"
"path" "path"
"strings" "strings"
"time" "time"
@ -32,6 +33,7 @@
"go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
"go.opentelemetry.io/otel/internal/global"
) )
const ( const (
@ -265,6 +267,24 @@ func WithEndpoint(endpoint string) GenericOption {
}) })
} }
func WithEndpointURL(v string) GenericOption {
return newGenericOption(func(cfg Config) Config {
u, err := url.Parse(v)
if err != nil {
global.Error(err, "otlptrace: parse endpoint url", "url", v)
return cfg
}
cfg.Traces.Endpoint = u.Host
cfg.Traces.URLPath = u.Path
if u.Scheme != "https" {
cfg.Traces.Insecure = true
}
return cfg
})
}
func WithCompression(compression Compression) GenericOption { func WithCompression(compression Compression) GenericOption {
return newGenericOption(func(cfg Config) Config { return newGenericOption(func(cfg Config) Config {
cfg.Traces.Compression = compression cfg.Traces.Compression = compression

View file

@ -64,14 +64,44 @@ func WithInsecure() Option {
return wrappedOption{otlpconfig.WithInsecure()} return wrappedOption{otlpconfig.WithInsecure()}
} }
// WithEndpoint sets the target endpoint the exporter will connect to. If // WithEndpoint sets the target endpoint the Exporter will connect to.
// unset, localhost:4317 will be used as a default. //
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
// environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// will take precedence.
//
// If both this option and WithEndpointURL are used, the last used option will
// take precedence.
//
// By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used.
// //
// This option has no effect if WithGRPCConn is used. // This option has no effect if WithGRPCConn is used.
func WithEndpoint(endpoint string) Option { func WithEndpoint(endpoint string) Option {
return wrappedOption{otlpconfig.WithEndpoint(endpoint)} return wrappedOption{otlpconfig.WithEndpoint(endpoint)}
} }
// WithEndpointURL sets the target endpoint URL the Exporter will connect to.
//
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
// environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// will take precedence.
//
// If both this option and WithEndpoint are used, the last used option will
// take precedence.
//
// If an invalid URL is provided, the default value will be kept.
//
// By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used.
//
// This option has no effect if WithGRPCConn is used.
func WithEndpointURL(u string) Option {
return wrappedOption{otlpconfig.WithEndpointURL(u)}
}
// WithReconnectionPeriod set the minimum amount of time between connection // WithReconnectionPeriod set the minimum amount of time between connection
// attempts to the target endpoint. // attempts to the target endpoint.
// //

View file

@ -177,8 +177,11 @@ func (d *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
if _, err := io.Copy(&respData, resp.Body); err != nil { if _, err := io.Copy(&respData, resp.Body); err != nil {
return err return err
} }
if respData.Len() == 0 {
return nil
}
if respData.Len() != 0 { if resp.Header.Get("Content-Type") == "application/x-protobuf" {
var respProto coltracepb.ExportTraceServiceResponse var respProto coltracepb.ExportTraceServiceResponse
if err := proto.Unmarshal(respData.Bytes(), &respProto); err != nil { if err := proto.Unmarshal(respData.Bytes(), &respProto); err != nil {
return err return err

View file

@ -26,14 +26,14 @@
The value may additionally contain a port and a path. The value may additionally contain a port and a path.
The value should not contain a query string or fragment. The value should not contain a query string or fragment.
The configuration can be overridden by OTEL_EXPORTER_OTLP_TRACES_ENDPOINT The configuration can be overridden by OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
environment variable and by [WithEndpoint], [WithInsecure] options. environment variable and by [WithEndpoint], [WithEndpointURL], [WithInsecure] options.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (default: "https://localhost:4318/v1/traces") - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (default: "https://localhost:4318/v1/traces") -
target URL to which the exporter sends telemetry. target URL to which the exporter sends telemetry.
The value must contain a scheme ("http" or "https") and host. The value must contain a scheme ("http" or "https") and host.
The value may additionally contain a port and a path. The value may additionally contain a port and a path.
The value should not contain a query string or fragment. The value should not contain a query string or fragment.
The configuration can be overridden by [WithEndpoint], [WitnInsecure], [WithURLPath] options. The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WitnInsecure], and [WithURLPath] options.
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS (default: none) - OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS (default: none) -
key-value pairs used as headers associated with HTTP requests. key-value pairs used as headers associated with HTTP requests.

View file

@ -20,6 +20,7 @@
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/url"
"path" "path"
"strings" "strings"
"time" "time"
@ -32,6 +33,7 @@
"go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/retry"
"go.opentelemetry.io/otel/internal/global"
) )
const ( const (
@ -265,6 +267,24 @@ func WithEndpoint(endpoint string) GenericOption {
}) })
} }
func WithEndpointURL(v string) GenericOption {
return newGenericOption(func(cfg Config) Config {
u, err := url.Parse(v)
if err != nil {
global.Error(err, "otlptrace: parse endpoint url", "url", v)
return cfg
}
cfg.Traces.Endpoint = u.Host
cfg.Traces.URLPath = u.Path
if u.Scheme != "https" {
cfg.Traces.Insecure = true
}
return cfg
})
}
func WithCompression(compression Compression) GenericOption { func WithCompression(compression Compression) GenericOption {
return newGenericOption(func(cfg Config) Config { return newGenericOption(func(cfg Config) Config {
cfg.Traces.Compression = compression cfg.Traces.Compression = compression

View file

@ -60,15 +60,44 @@ func (w wrappedOption) applyHTTPOption(cfg otlpconfig.Config) otlpconfig.Config
return w.ApplyHTTPOption(cfg) return w.ApplyHTTPOption(cfg)
} }
// WithEndpoint allows one to set the address of the collector // WithEndpoint sets the target endpoint the Exporter will connect to.
// endpoint that the driver will use to send spans. If //
// unset, it will instead try to use // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
// the default endpoint (localhost:4318). Note that the endpoint // environment variable is set, and this option is not passed, that variable
// must not contain any URL path. // value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// will take precedence.
//
// If both this option and WithEndpointURL are used, the last used option will
// take precedence.
//
// By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used.
//
// This option has no effect if WithGRPCConn is used.
func WithEndpoint(endpoint string) Option { func WithEndpoint(endpoint string) Option {
return wrappedOption{otlpconfig.WithEndpoint(endpoint)} return wrappedOption{otlpconfig.WithEndpoint(endpoint)}
} }
// WithEndpointURL sets the target endpoint URL the Exporter will connect to.
//
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
// environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// will take precedence.
//
// If both this option and WithEndpoint are used, the last used option will
// take precedence.
//
// If an invalid URL is provided, the default value will be kept.
//
// By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used.
//
// This option has no effect if WithGRPCConn is used.
func WithEndpointURL(u string) Option {
return wrappedOption{otlpconfig.WithEndpointURL(u)}
}
// WithCompression tells the driver to compress the sent data. // WithCompression tells the driver to compress the sent data.
func WithCompression(compression Compression) Option { func WithCompression(compression Compression) Option {
return wrappedOption{otlpconfig.WithCompression(otlpconfig.Compression(compression))} return wrappedOption{otlpconfig.WithCompression(otlpconfig.Compression(compression))}

View file

@ -16,5 +16,5 @@
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use. // Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
func Version() string { func Version() string {
return "1.20.0" return "1.24.0"
} }

View file

@ -19,18 +19,20 @@
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric"
) )
// config contains options for the exporter. // config contains options for the exporter.
type config struct { type config struct {
registerer prometheus.Registerer registerer prometheus.Registerer
disableTargetInfo bool disableTargetInfo bool
withoutUnits bool withoutUnits bool
withoutCounterSuffixes bool withoutCounterSuffixes bool
readerOpts []metric.ManualReaderOption readerOpts []metric.ManualReaderOption
disableScopeInfo bool disableScopeInfo bool
namespace string namespace string
resourceAttributesFilter attribute.Filter
} }
// newConfig creates a validated config configured with options. // newConfig creates a validated config configured with options.
@ -151,3 +153,14 @@ func WithNamespace(ns string) Option {
return cfg return cfg
}) })
} }
// WithResourceAsConstantLabels configures the Exporter to add the resource attributes the
// resourceFilter returns true for as attributes on all exported metrics.
//
// The does not affect the target info generated from resource attributes.
func WithResourceAsConstantLabels(resourceFilter attribute.Filter) Option {
return optionFunc(func(cfg config) config {
cfg.resourceAttributesFilter = resourceFilter
return cfg
})
}

View file

@ -78,14 +78,21 @@ func (e *Exporter) MarshalLog() interface{} {
var _ metric.Reader = &Exporter{} var _ metric.Reader = &Exporter{}
// keyVals is used to store resource attribute key value pairs.
type keyVals struct {
keys []string
vals []string
}
// collector is used to implement prometheus.Collector. // collector is used to implement prometheus.Collector.
type collector struct { type collector struct {
reader metric.Reader reader metric.Reader
withoutUnits bool withoutUnits bool
withoutCounterSuffixes bool withoutCounterSuffixes bool
disableScopeInfo bool disableScopeInfo bool
namespace string namespace string
resourceAttributesFilter attribute.Filter
mu sync.Mutex // mu protects all members below from the concurrent access. mu sync.Mutex // mu protects all members below from the concurrent access.
disableTargetInfo bool disableTargetInfo bool
@ -93,6 +100,7 @@ type collector struct {
scopeInfos map[instrumentation.Scope]prometheus.Metric scopeInfos map[instrumentation.Scope]prometheus.Metric
scopeInfosInvalid map[instrumentation.Scope]struct{} scopeInfosInvalid map[instrumentation.Scope]struct{}
metricFamilies map[string]*dto.MetricFamily metricFamilies map[string]*dto.MetricFamily
resourceKeyVals keyVals
} }
// prometheus counters MUST have a _total suffix by default: // prometheus counters MUST have a _total suffix by default:
@ -109,15 +117,16 @@ func New(opts ...Option) (*Exporter, error) {
reader := metric.NewManualReader(cfg.readerOpts...) reader := metric.NewManualReader(cfg.readerOpts...)
collector := &collector{ collector := &collector{
reader: reader, reader: reader,
disableTargetInfo: cfg.disableTargetInfo, disableTargetInfo: cfg.disableTargetInfo,
withoutUnits: cfg.withoutUnits, withoutUnits: cfg.withoutUnits,
withoutCounterSuffixes: cfg.withoutCounterSuffixes, withoutCounterSuffixes: cfg.withoutCounterSuffixes,
disableScopeInfo: cfg.disableScopeInfo, disableScopeInfo: cfg.disableScopeInfo,
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric), scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
scopeInfosInvalid: make(map[instrumentation.Scope]struct{}), scopeInfosInvalid: make(map[instrumentation.Scope]struct{}),
metricFamilies: make(map[string]*dto.MetricFamily), metricFamilies: make(map[string]*dto.MetricFamily),
namespace: cfg.namespace, namespace: cfg.namespace,
resourceAttributesFilter: cfg.resourceAttributesFilter,
} }
if err := cfg.registerer.Register(collector); err != nil { if err := cfg.registerer.Register(collector); err != nil {
@ -181,6 +190,10 @@ func() {
ch <- c.targetInfo ch <- c.targetInfo
} }
if c.resourceAttributesFilter != nil && len(c.resourceKeyVals.keys) == 0 {
c.createResourceAttributes(metrics.Resource)
}
for _, scopeMetrics := range metrics.ScopeMetrics { for _, scopeMetrics := range metrics.ScopeMetrics {
var keys, values [2]string var keys, values [2]string
@ -219,26 +232,26 @@ func() {
switch v := m.Data.(type) { switch v := m.Data.(type) {
case metricdata.Histogram[int64]: case metricdata.Histogram[int64]:
addHistogramMetric(ch, v, m, keys, values, name) addHistogramMetric(ch, v, m, keys, values, name, c.resourceKeyVals)
case metricdata.Histogram[float64]: case metricdata.Histogram[float64]:
addHistogramMetric(ch, v, m, keys, values, name) addHistogramMetric(ch, v, m, keys, values, name, c.resourceKeyVals)
case metricdata.Sum[int64]: case metricdata.Sum[int64]:
addSumMetric(ch, v, m, keys, values, name) addSumMetric(ch, v, m, keys, values, name, c.resourceKeyVals)
case metricdata.Sum[float64]: case metricdata.Sum[float64]:
addSumMetric(ch, v, m, keys, values, name) addSumMetric(ch, v, m, keys, values, name, c.resourceKeyVals)
case metricdata.Gauge[int64]: case metricdata.Gauge[int64]:
addGaugeMetric(ch, v, m, keys, values, name) addGaugeMetric(ch, v, m, keys, values, name, c.resourceKeyVals)
case metricdata.Gauge[float64]: case metricdata.Gauge[float64]:
addGaugeMetric(ch, v, m, keys, values, name) addGaugeMetric(ch, v, m, keys, values, name, c.resourceKeyVals)
} }
} }
} }
} }
func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.Histogram[N], m metricdata.Metrics, ks, vs [2]string, name string) { func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.Histogram[N], m metricdata.Metrics, ks, vs [2]string, name string, resourceKV keyVals) {
// TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars // TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars
for _, dp := range histogram.DataPoints { for _, dp := range histogram.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs) keys, values := getAttrs(dp.Attributes, ks, vs, resourceKV)
desc := prometheus.NewDesc(name, m.Description, keys, nil) desc := prometheus.NewDesc(name, m.Description, keys, nil)
buckets := make(map[float64]uint64, len(dp.Bounds)) buckets := make(map[float64]uint64, len(dp.Bounds))
@ -257,14 +270,14 @@ func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogra
} }
} }
func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata.Sum[N], m metricdata.Metrics, ks, vs [2]string, name string) { func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata.Sum[N], m metricdata.Metrics, ks, vs [2]string, name string, resourceKV keyVals) {
valueType := prometheus.CounterValue valueType := prometheus.CounterValue
if !sum.IsMonotonic { if !sum.IsMonotonic {
valueType = prometheus.GaugeValue valueType = prometheus.GaugeValue
} }
for _, dp := range sum.DataPoints { for _, dp := range sum.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs) keys, values := getAttrs(dp.Attributes, ks, vs, resourceKV)
desc := prometheus.NewDesc(name, m.Description, keys, nil) desc := prometheus.NewDesc(name, m.Description, keys, nil)
m, err := prometheus.NewConstMetric(desc, valueType, float64(dp.Value), values...) m, err := prometheus.NewConstMetric(desc, valueType, float64(dp.Value), values...)
@ -276,9 +289,9 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata
} }
} }
func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metricdata.Gauge[N], m metricdata.Metrics, ks, vs [2]string, name string) { func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metricdata.Gauge[N], m metricdata.Metrics, ks, vs [2]string, name string, resourceKV keyVals) {
for _, dp := range gauge.DataPoints { for _, dp := range gauge.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs) keys, values := getAttrs(dp.Attributes, ks, vs, resourceKV)
desc := prometheus.NewDesc(name, m.Description, keys, nil) desc := prometheus.NewDesc(name, m.Description, keys, nil)
m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(dp.Value), values...) m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(dp.Value), values...)
@ -293,7 +306,7 @@ func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metric
// getAttrs parses the attribute.Set to two lists of matching Prometheus-style // getAttrs parses the attribute.Set to two lists of matching Prometheus-style
// keys and values. It sanitizes invalid characters and handles duplicate keys // keys and values. It sanitizes invalid characters and handles duplicate keys
// (due to sanitization) by sorting and concatenating the values following the spec. // (due to sanitization) by sorting and concatenating the values following the spec.
func getAttrs(attrs attribute.Set, ks, vs [2]string) ([]string, []string) { func getAttrs(attrs attribute.Set, ks, vs [2]string, resourceKV keyVals) ([]string, []string) {
keysMap := make(map[string][]string) keysMap := make(map[string][]string)
itr := attrs.Iter() itr := attrs.Iter()
for itr.Next() { for itr.Next() {
@ -321,11 +334,17 @@ func getAttrs(attrs attribute.Set, ks, vs [2]string) ([]string, []string) {
keys = append(keys, ks[:]...) keys = append(keys, ks[:]...)
values = append(values, vs[:]...) values = append(values, vs[:]...)
} }
for idx := range resourceKV.keys {
keys = append(keys, resourceKV.keys[idx])
values = append(values, resourceKV.vals[idx])
}
return keys, values return keys, values
} }
func createInfoMetric(name, description string, res *resource.Resource) (prometheus.Metric, error) { func createInfoMetric(name, description string, res *resource.Resource) (prometheus.Metric, error) {
keys, values := getAttrs(*res.Set(), [2]string{}, [2]string{}) keys, values := getAttrs(*res.Set(), [2]string{}, [2]string{}, keyVals{})
desc := prometheus.NewDesc(name, description, keys, nil) desc := prometheus.NewDesc(name, description, keys, nil)
return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...) return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...)
} }
@ -473,6 +492,15 @@ func (c *collector) metricType(m metricdata.Metrics) *dto.MetricType {
return nil return nil
} }
func (c *collector) createResourceAttributes(res *resource.Resource) {
c.mu.Lock()
defer c.mu.Unlock()
resourceAttrs, _ := res.Set().Filter(c.resourceAttributesFilter)
resourceKeys, resourceValues := getAttrs(resourceAttrs, [2]string{}, [2]string{}, keyVals{})
c.resourceKeyVals = keyVals{keys: resourceKeys, vals: resourceValues}
}
func (c *collector) scopeInfo(scope instrumentation.Scope) (prometheus.Metric, error) { func (c *collector) scopeInfo(scope instrumentation.Scope) (prometheus.Metric, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()

View file

@ -130,9 +130,11 @@ func (m *meter) setDelegate(provider metric.MeterProvider) {
inst.setDelegate(meter) inst.setDelegate(meter)
} }
for e := m.registry.Front(); e != nil; e = e.Next() { var n *list.Element
for e := m.registry.Front(); e != nil; e = n {
r := e.Value.(*registration) r := e.Value.(*registration)
r.setDelegate(meter) r.setDelegate(meter)
n = e.Next()
m.registry.Remove(e) m.registry.Remove(e)
} }

View file

@ -63,7 +63,7 @@ func SetTracerProvider(tp trace.TracerProvider) {
// to itself. // to itself.
Error( Error(
errors.New("no delegate configured in tracer provider"), errors.New("no delegate configured in tracer provider"),
"Setting tracer provider to it's current value. No delegate will be configured", "Setting tracer provider to its current value. No delegate will be configured",
) )
return return
} }
@ -92,7 +92,7 @@ func SetTextMapPropagator(p propagation.TextMapPropagator) {
// delegate to itself. // delegate to itself.
Error( Error(
errors.New("no delegate configured in text map propagator"), errors.New("no delegate configured in text map propagator"),
"Setting text map propagator to it's current value. No delegate will be configured", "Setting text map propagator to its current value. No delegate will be configured",
) )
return return
} }
@ -123,7 +123,7 @@ func SetMeterProvider(mp metric.MeterProvider) {
// to itself. // to itself.
Error( Error(
errors.New("no delegate configured in meter provider"), errors.New("no delegate configured in meter provider"),
"Setting meter provider to it's current value. No delegate will be configured", "Setting meter provider to its current value. No delegate will be configured",
) )
return return
} }

View file

@ -18,7 +18,7 @@
"context" "context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"regexp" "strings"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
@ -28,6 +28,7 @@
maxVersion = 254 maxVersion = 254
traceparentHeader = "traceparent" traceparentHeader = "traceparent"
tracestateHeader = "tracestate" tracestateHeader = "tracestate"
delimiter = "-"
) )
// TraceContext is a propagator that supports the W3C Trace Context format // TraceContext is a propagator that supports the W3C Trace Context format
@ -41,8 +42,8 @@
type TraceContext struct{} type TraceContext struct{}
var ( var (
_ TextMapPropagator = TraceContext{} _ TextMapPropagator = TraceContext{}
traceCtxRegExp = regexp.MustCompile("^(?P<version>[0-9a-f]{2})-(?P<traceID>[a-f0-9]{32})-(?P<spanID>[a-f0-9]{16})-(?P<traceFlags>[a-f0-9]{2})(?:-.*)?$") versionPart = fmt.Sprintf("%.2X", supportedVersion)
) )
// Inject set tracecontext from the Context into the carrier. // Inject set tracecontext from the Context into the carrier.
@ -59,12 +60,19 @@ func (tc TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) {
// Clear all flags other than the trace-context supported sampling bit. // Clear all flags other than the trace-context supported sampling bit.
flags := sc.TraceFlags() & trace.FlagsSampled flags := sc.TraceFlags() & trace.FlagsSampled
h := fmt.Sprintf("%.2x-%s-%s-%s", var sb strings.Builder
supportedVersion, sb.Grow(2 + 32 + 16 + 2 + 3)
sc.TraceID(), _, _ = sb.WriteString(versionPart)
sc.SpanID(), traceID := sc.TraceID()
flags) spanID := sc.SpanID()
carrier.Set(traceparentHeader, h) flagByte := [1]byte{byte(flags)}
var buf [32]byte
for _, src := range [][]byte{traceID[:], spanID[:], flagByte[:]} {
_ = sb.WriteByte(delimiter[0])
n := hex.Encode(buf[:], src)
_, _ = sb.Write(buf[:n])
}
carrier.Set(traceparentHeader, sb.String())
} }
// Extract reads tracecontext from the carrier into a returned Context. // Extract reads tracecontext from the carrier into a returned Context.
@ -86,21 +94,8 @@ func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
return trace.SpanContext{} return trace.SpanContext{}
} }
matches := traceCtxRegExp.FindStringSubmatch(h) var ver [1]byte
if !extractPart(ver[:], &h, 2) {
if len(matches) == 0 {
return trace.SpanContext{}
}
if len(matches) < 5 { // four subgroups plus the overall match
return trace.SpanContext{}
}
if len(matches[1]) != 2 {
return trace.SpanContext{}
}
ver, err := hex.DecodeString(matches[1])
if err != nil {
return trace.SpanContext{} return trace.SpanContext{}
} }
version := int(ver[0]) version := int(ver[0])
@ -108,36 +103,24 @@ func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
return trace.SpanContext{} return trace.SpanContext{}
} }
if version == 0 && len(matches) != 5 { // four subgroups plus the overall match
return trace.SpanContext{}
}
if len(matches[2]) != 32 {
return trace.SpanContext{}
}
var scc trace.SpanContextConfig var scc trace.SpanContextConfig
if !extractPart(scc.TraceID[:], &h, 32) {
scc.TraceID, err = trace.TraceIDFromHex(matches[2][:32]) return trace.SpanContext{}
if err != nil { }
if !extractPart(scc.SpanID[:], &h, 16) {
return trace.SpanContext{} return trace.SpanContext{}
} }
if len(matches[3]) != 16 { var opts [1]byte
if !extractPart(opts[:], &h, 2) {
return trace.SpanContext{} return trace.SpanContext{}
} }
scc.SpanID, err = trace.SpanIDFromHex(matches[3]) if version == 0 && (h != "" || opts[0] > 2) {
if err != nil { // version 0 not allow extra
// version 0 not allow other flag
return trace.SpanContext{} return trace.SpanContext{}
} }
if len(matches[4]) != 2 {
return trace.SpanContext{}
}
opts, err := hex.DecodeString(matches[4])
if err != nil || len(opts) < 1 || (version == 0 && opts[0] > 2) {
return trace.SpanContext{}
}
// Clear all flags other than the trace-context supported sampling bit. // Clear all flags other than the trace-context supported sampling bit.
scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled
@ -155,6 +138,29 @@ func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
return sc return sc
} }
// upperHex detect hex is upper case Unicode characters.
func upperHex(v string) bool {
for _, c := range v {
if c >= 'A' && c <= 'F' {
return true
}
}
return false
}
func extractPart(dst []byte, h *string, n int) bool {
part, left, _ := strings.Cut(*h, delimiter)
*h = left
// hex.Decode decodes unsupported upper-case characters, so exclude explicitly.
if len(part) != n || upperHex(part) {
return false
}
if p, err := hex.Decode(dst, []byte(part)); err != nil || p != n/2 {
return false
}
return true
}
// Fields returns the keys who's values are set with Inject. // Fields returns the keys who's values are set with Inject.
func (tc TraceContext) Fields() []string { func (tc TraceContext) Fields() []string {
return []string{traceparentHeader, tracestateHeader} return []string{traceparentHeader, tracestateHeader}

View file

@ -52,3 +52,43 @@ func (c *cache[K, V]) Lookup(key K, f func() V) V {
c.data[key] = val c.data[key] = val
return val return val
} }
// HasKey returns true if Lookup has previously been called with that key
//
// HasKey is safe to call concurrently.
func (c *cache[K, V]) HasKey(key K) bool {
c.Lock()
defer c.Unlock()
_, ok := c.data[key]
return ok
}
// cacheWithErr is a locking storage used to quickly return already computed values and an error.
//
// The zero value of a cacheWithErr is empty and ready to use.
//
// A cacheWithErr must not be copied after first use.
//
// All methods of a cacheWithErr are safe to call concurrently.
type cacheWithErr[K comparable, V any] struct {
cache[K, valAndErr[V]]
}
type valAndErr[V any] struct {
val V
err error
}
// Lookup returns the value stored in the cacheWithErr with the associated key
// if it exists. Otherwise, f is called and its returned value is set in the
// cacheWithErr for key and returned.
//
// Lookup is safe to call concurrently. It will hold the cacheWithErr lock, so f
// should not block excessively.
func (c *cacheWithErr[K, V]) Lookup(key K, f func() (V, error)) (V, error) {
combined := c.cache.Lookup(key, func() valAndErr[V] {
val, err := f()
return valAndErr[V]{val: val, err: err}
})
return combined.val, combined.err
}

View file

@ -44,4 +44,7 @@
// //
// See [go.opentelemetry.io/otel/metric] for more information about // See [go.opentelemetry.io/otel/metric] for more information about
// the metric API. // the metric API.
//
// See [go.opentelemetry.io/otel/sdk/metric/internal/x] for information about
// the experimental features.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"

96
vendor/go.opentelemetry.io/otel/sdk/metric/exemplar.go generated vendored Normal file
View file

@ -0,0 +1,96 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric"
import (
"os"
"runtime"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/internal/x"
)
// reservoirFunc returns the appropriately configured exemplar reservoir
// creation func based on the passed InstrumentKind and user defined
// environment variables.
//
// Note: This will only return non-nil values when the experimental exemplar
// feature is enabled and the OTEL_METRICS_EXEMPLAR_FILTER environment variable
// is not set to always_off.
func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.Reservoir[N] {
if !x.Exemplars.Enabled() {
return nil
}
// https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/metrics/sdk.md#exemplar-defaults
resF := func() func() exemplar.Reservoir[N] {
// Explicit bucket histogram aggregation with more than 1 bucket will
// use AlignedHistogramBucketExemplarReservoir.
a, ok := agg.(AggregationExplicitBucketHistogram)
if ok && len(a.Boundaries) > 0 {
cp := make([]float64, len(a.Boundaries))
copy(cp, a.Boundaries)
return func() exemplar.Reservoir[N] {
bounds := cp
return exemplar.Histogram[N](bounds)
}
}
var n int
if a, ok := agg.(AggregationBase2ExponentialHistogram); ok {
// Base2 Exponential Histogram Aggregation SHOULD use a
// SimpleFixedSizeExemplarReservoir with a reservoir equal to the
// smaller of the maximum number of buckets configured on the
// aggregation or twenty (e.g. min(20, max_buckets)).
n = int(a.MaxSize)
if n > 20 {
n = 20
}
} else {
// https://github.com/open-telemetry/opentelemetry-specification/blob/e94af89e3d0c01de30127a0f423e912f6cda7bed/specification/metrics/sdk.md#simplefixedsizeexemplarreservoir
// This Exemplar reservoir MAY take a configuration parameter for
// the size of the reservoir. If no size configuration is
// provided, the default size MAY be the number of possible
// concurrent threads (e.g. number of CPUs) to help reduce
// contention. Otherwise, a default size of 1 SHOULD be used.
n = runtime.NumCPU()
if n < 1 {
// Should never be the case, but be defensive.
n = 1
}
}
return func() exemplar.Reservoir[N] {
return exemplar.FixedSize[N](n)
}
}
// https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/configuration/sdk-environment-variables.md#exemplar
const filterEnvKey = "OTEL_METRICS_EXEMPLAR_FILTER"
switch os.Getenv(filterEnvKey) {
case "always_on":
return resF()
case "always_off":
return exemplar.Drop[N]
case "trace_based":
fallthrough
default:
newR := resF()
return func() exemplar.Reservoir[N] {
return exemplar.SampledFilter(newR())
}
}
}

View file

@ -205,9 +205,6 @@ func (i *int64Inst) Record(ctx context.Context, val int64, opts ...metric.Record
} }
func (i *int64Inst) aggregate(ctx context.Context, val int64, s attribute.Set) { // nolint:revive // okay to shadow pkg with method. func (i *int64Inst) aggregate(ctx context.Context, val int64, s attribute.Set) { // nolint:revive // okay to shadow pkg with method.
if err := ctx.Err(); err != nil {
return
}
for _, in := range i.measures { for _, in := range i.measures {
in(ctx, val, s) in(ctx, val, s)
} }
@ -238,9 +235,6 @@ func (i *float64Inst) Record(ctx context.Context, val float64, opts ...metric.Re
} }
func (i *float64Inst) aggregate(ctx context.Context, val float64, s attribute.Set) { func (i *float64Inst) aggregate(ctx context.Context, val float64, s attribute.Set) {
if err := ctx.Err(); err != nil {
return
}
for _, in := range i.measures { for _, in := range i.measures {
in(ctx, val, s) in(ctx, val, s)
} }
@ -270,9 +264,9 @@ type float64Observable struct {
_ metric.Float64ObservableGauge = float64Observable{} _ metric.Float64ObservableGauge = float64Observable{}
) )
func newFloat64Observable(m *meter, kind InstrumentKind, name, desc, u string, meas []aggregate.Measure[float64]) float64Observable { func newFloat64Observable(m *meter, kind InstrumentKind, name, desc, u string) float64Observable {
return float64Observable{ return float64Observable{
observable: newObservable(m, kind, name, desc, u, meas), observable: newObservable[float64](m, kind, name, desc, u),
} }
} }
@ -291,9 +285,9 @@ type int64Observable struct {
_ metric.Int64ObservableGauge = int64Observable{} _ metric.Int64ObservableGauge = int64Observable{}
) )
func newInt64Observable(m *meter, kind InstrumentKind, name, desc, u string, meas []aggregate.Measure[int64]) int64Observable { func newInt64Observable(m *meter, kind InstrumentKind, name, desc, u string) int64Observable {
return int64Observable{ return int64Observable{
observable: newObservable(m, kind, name, desc, u, meas), observable: newObservable[int64](m, kind, name, desc, u),
} }
} }
@ -301,11 +295,12 @@ type observable[N int64 | float64] struct {
metric.Observable metric.Observable
observablID[N] observablID[N]
meter *meter meter *meter
measures []aggregate.Measure[N] measures measures[N]
dropAggregation bool
} }
func newObservable[N int64 | float64](m *meter, kind InstrumentKind, name, desc, u string, meas []aggregate.Measure[N]) *observable[N] { func newObservable[N int64 | float64](m *meter, kind InstrumentKind, name, desc, u string) *observable[N] {
return &observable[N]{ return &observable[N]{
observablID: observablID[N]{ observablID: observablID[N]{
name: name, name: name,
@ -314,14 +309,24 @@ func newObservable[N int64 | float64](m *meter, kind InstrumentKind, name, desc,
unit: u, unit: u,
scope: m.scope, scope: m.scope,
}, },
meter: m, meter: m,
measures: meas,
} }
} }
// observe records the val for the set of attrs. // observe records the val for the set of attrs.
func (o *observable[N]) observe(val N, s attribute.Set) { func (o *observable[N]) observe(val N, s attribute.Set) {
for _, in := range o.measures { o.measures.observe(val, s)
}
func (o *observable[N]) appendMeasures(meas []aggregate.Measure[N]) {
o.measures = append(o.measures, meas...)
}
type measures[N int64 | float64] []aggregate.Measure[N]
// observe records the val for the set of attrs.
func (m measures[N]) observe(val N, s attribute.Set) {
for _, in := range m {
in(context.Background(), val, s) in(context.Background(), val, s)
} }
} }

View file

@ -19,6 +19,7 @@
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
@ -44,17 +45,43 @@ type Builder[N int64 | float64] struct {
// Filter is the attribute filter the aggregate function will use on the // Filter is the attribute filter the aggregate function will use on the
// input of measurements. // input of measurements.
Filter attribute.Filter Filter attribute.Filter
// ReservoirFunc is the factory function used by aggregate functions to
// create new exemplar reservoirs for a new seen attribute set.
//
// If this is not provided a default factory function that returns an
// exemplar.Drop reservoir will be used.
ReservoirFunc func() exemplar.Reservoir[N]
// AggregationLimit is the cardinality limit of measurement attributes. Any
// measurement for new attributes once the limit has been reached will be
// aggregated into a single aggregate for the "otel.metric.overflow"
// attribute.
//
// If AggregationLimit is less than or equal to zero there will not be an
// aggregation limit imposed (i.e. unlimited attribute sets).
AggregationLimit int
} }
func (b Builder[N]) filter(f Measure[N]) Measure[N] { func (b Builder[N]) resFunc() func() exemplar.Reservoir[N] {
if b.ReservoirFunc != nil {
return b.ReservoirFunc
}
return exemplar.Drop[N]
}
type fltrMeasure[N int64 | float64] func(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue)
func (b Builder[N]) filter(f fltrMeasure[N]) Measure[N] {
if b.Filter != nil { if b.Filter != nil {
fltr := b.Filter // Copy to make it immutable after assignment. fltr := b.Filter // Copy to make it immutable after assignment.
return func(ctx context.Context, n N, a attribute.Set) { return func(ctx context.Context, n N, a attribute.Set) {
fAttr, _ := a.Filter(fltr) fAttr, dropped := a.Filter(fltr)
f(ctx, n, fAttr) f(ctx, n, fAttr, dropped)
} }
} }
return f return func(ctx context.Context, n N, a attribute.Set) {
f(ctx, n, a, nil)
}
} }
// LastValue returns a last-value aggregate function input and output. // LastValue returns a last-value aggregate function input and output.
@ -63,7 +90,7 @@ func (b Builder[N]) filter(f Measure[N]) Measure[N] {
func (b Builder[N]) LastValue() (Measure[N], ComputeAggregation) { func (b Builder[N]) LastValue() (Measure[N], ComputeAggregation) {
// Delta temporality is the only temporality that makes semantic sense for // Delta temporality is the only temporality that makes semantic sense for
// a last-value aggregate. // a last-value aggregate.
lv := newLastValue[N]() lv := newLastValue[N](b.AggregationLimit, b.resFunc())
return b.filter(lv.measure), func(dest *metricdata.Aggregation) int { return b.filter(lv.measure), func(dest *metricdata.Aggregation) int {
// Ignore if dest is not a metricdata.Gauge. The chance for memory // Ignore if dest is not a metricdata.Gauge. The chance for memory
@ -79,7 +106,7 @@ func (b Builder[N]) LastValue() (Measure[N], ComputeAggregation) {
// PrecomputedSum returns a sum aggregate function input and output. The // PrecomputedSum returns a sum aggregate function input and output. The
// arguments passed to the input are expected to be the precomputed sum values. // arguments passed to the input are expected to be the precomputed sum values.
func (b Builder[N]) PrecomputedSum(monotonic bool) (Measure[N], ComputeAggregation) { func (b Builder[N]) PrecomputedSum(monotonic bool) (Measure[N], ComputeAggregation) {
s := newPrecomputedSum[N](monotonic) s := newPrecomputedSum[N](monotonic, b.AggregationLimit, b.resFunc())
switch b.Temporality { switch b.Temporality {
case metricdata.DeltaTemporality: case metricdata.DeltaTemporality:
return b.filter(s.measure), s.delta return b.filter(s.measure), s.delta
@ -90,7 +117,7 @@ func (b Builder[N]) PrecomputedSum(monotonic bool) (Measure[N], ComputeAggregati
// Sum returns a sum aggregate function input and output. // Sum returns a sum aggregate function input and output.
func (b Builder[N]) Sum(monotonic bool) (Measure[N], ComputeAggregation) { func (b Builder[N]) Sum(monotonic bool) (Measure[N], ComputeAggregation) {
s := newSum[N](monotonic) s := newSum[N](monotonic, b.AggregationLimit, b.resFunc())
switch b.Temporality { switch b.Temporality {
case metricdata.DeltaTemporality: case metricdata.DeltaTemporality:
return b.filter(s.measure), s.delta return b.filter(s.measure), s.delta
@ -102,7 +129,7 @@ func (b Builder[N]) Sum(monotonic bool) (Measure[N], ComputeAggregation) {
// ExplicitBucketHistogram returns a histogram aggregate function input and // ExplicitBucketHistogram returns a histogram aggregate function input and
// output. // output.
func (b Builder[N]) ExplicitBucketHistogram(boundaries []float64, noMinMax, noSum bool) (Measure[N], ComputeAggregation) { func (b Builder[N]) ExplicitBucketHistogram(boundaries []float64, noMinMax, noSum bool) (Measure[N], ComputeAggregation) {
h := newHistogram[N](boundaries, noMinMax, noSum) h := newHistogram[N](boundaries, noMinMax, noSum, b.AggregationLimit, b.resFunc())
switch b.Temporality { switch b.Temporality {
case metricdata.DeltaTemporality: case metricdata.DeltaTemporality:
return b.filter(h.measure), h.delta return b.filter(h.measure), h.delta
@ -114,7 +141,7 @@ func (b Builder[N]) ExplicitBucketHistogram(boundaries []float64, noMinMax, noSu
// ExponentialBucketHistogram returns a histogram aggregate function input and // ExponentialBucketHistogram returns a histogram aggregate function input and
// output. // output.
func (b Builder[N]) ExponentialBucketHistogram(maxSize, maxScale int32, noMinMax, noSum bool) (Measure[N], ComputeAggregation) { func (b Builder[N]) ExponentialBucketHistogram(maxSize, maxScale int32, noMinMax, noSum bool) (Measure[N], ComputeAggregation) {
h := newExponentialHistogram[N](maxSize, maxScale, noMinMax, noSum) h := newExponentialHistogram[N](maxSize, maxScale, noMinMax, noSum, b.AggregationLimit, b.resFunc())
switch b.Temporality { switch b.Temporality {
case metricdata.DeltaTemporality: case metricdata.DeltaTemporality:
return b.filter(h.measure), h.delta return b.filter(h.measure), h.delta

View file

@ -23,6 +23,7 @@
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
@ -40,6 +41,8 @@
// expoHistogramDataPoint is a single data point in an exponential histogram. // expoHistogramDataPoint is a single data point in an exponential histogram.
type expoHistogramDataPoint[N int64 | float64] struct { type expoHistogramDataPoint[N int64 | float64] struct {
res exemplar.Reservoir[N]
count uint64 count uint64
min N min N
max N max N
@ -288,13 +291,15 @@ func (b *expoBuckets) downscale(delta int) {
// newExponentialHistogram returns an Aggregator that summarizes a set of // newExponentialHistogram returns an Aggregator that summarizes a set of
// measurements as an exponential histogram. Each histogram is scoped by attributes // measurements as an exponential histogram. Each histogram is scoped by attributes
// and the aggregation cycle the measurements were made in. // and the aggregation cycle the measurements were made in.
func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool) *expoHistogram[N] { func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir[N]) *expoHistogram[N] {
return &expoHistogram[N]{ return &expoHistogram[N]{
noSum: noSum, noSum: noSum,
noMinMax: noMinMax, noMinMax: noMinMax,
maxSize: int(maxSize), maxSize: int(maxSize),
maxScale: int(maxScale), maxScale: int(maxScale),
newRes: r,
limit: newLimiter[*expoHistogramDataPoint[N]](limit),
values: make(map[attribute.Set]*expoHistogramDataPoint[N]), values: make(map[attribute.Set]*expoHistogramDataPoint[N]),
start: now(), start: now(),
@ -309,27 +314,35 @@ type expoHistogram[N int64 | float64] struct {
maxSize int maxSize int
maxScale int maxScale int
newRes func() exemplar.Reservoir[N]
limit limiter[*expoHistogramDataPoint[N]]
values map[attribute.Set]*expoHistogramDataPoint[N] values map[attribute.Set]*expoHistogramDataPoint[N]
valuesMu sync.Mutex valuesMu sync.Mutex
start time.Time start time.Time
} }
func (e *expoHistogram[N]) measure(_ context.Context, value N, attr attribute.Set) { func (e *expoHistogram[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) {
// Ignore NaN and infinity. // Ignore NaN and infinity.
if math.IsInf(float64(value), 0) || math.IsNaN(float64(value)) { if math.IsInf(float64(value), 0) || math.IsNaN(float64(value)) {
return return
} }
t := now()
e.valuesMu.Lock() e.valuesMu.Lock()
defer e.valuesMu.Unlock() defer e.valuesMu.Unlock()
attr := e.limit.Attributes(fltrAttr, e.values)
v, ok := e.values[attr] v, ok := e.values[attr]
if !ok { if !ok {
v = newExpoHistogramDataPoint[N](e.maxSize, e.maxScale, e.noMinMax, e.noSum) v = newExpoHistogramDataPoint[N](e.maxSize, e.maxScale, e.noMinMax, e.noSum)
v.res = e.newRes()
e.values[attr] = v e.values[attr] = v
} }
v.record(value) v.record(value)
v.res.Offer(ctx, t, value, droppedAttr)
} }
func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int { func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
@ -362,6 +375,7 @@ func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
hDPts[i].NegativeBucket.Offset = int32(b.negBuckets.startBin) hDPts[i].NegativeBucket.Offset = int32(b.negBuckets.startBin)
hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(b.negBuckets.counts), len(b.negBuckets.counts)) hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(b.negBuckets.counts), len(b.negBuckets.counts))
copy(hDPts[i].NegativeBucket.Counts, b.negBuckets.counts)
if !e.noSum { if !e.noSum {
hDPts[i].Sum = b.sum hDPts[i].Sum = b.sum
@ -371,6 +385,8 @@ func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(b.max)
} }
b.res.Collect(&hDPts[i].Exemplars)
delete(e.values, a) delete(e.values, a)
i++ i++
} }
@ -410,6 +426,7 @@ func (e *expoHistogram[N]) cumulative(dest *metricdata.Aggregation) int {
hDPts[i].NegativeBucket.Offset = int32(b.negBuckets.startBin) hDPts[i].NegativeBucket.Offset = int32(b.negBuckets.startBin)
hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(b.negBuckets.counts), len(b.negBuckets.counts)) hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(b.negBuckets.counts), len(b.negBuckets.counts))
copy(hDPts[i].NegativeBucket.Counts, b.negBuckets.counts)
if !e.noSum { if !e.noSum {
hDPts[i].Sum = b.sum hDPts[i].Sum = b.sum
@ -419,6 +436,8 @@ func (e *expoHistogram[N]) cumulative(dest *metricdata.Aggregation) int {
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(b.max)
} }
b.res.Collect(&hDPts[i].Exemplars)
i++ i++
// TODO (#3006): This will use an unbounded amount of memory if there // TODO (#3006): This will use an unbounded amount of memory if there
// are unbounded number of attribute sets being aggregated. Attribute // are unbounded number of attribute sets being aggregated. Attribute

View file

@ -21,10 +21,13 @@
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
type buckets[N int64 | float64] struct { type buckets[N int64 | float64] struct {
res exemplar.Reservoir[N]
counts []uint64 counts []uint64
count uint64 count uint64
total N total N
@ -54,11 +57,13 @@ type histValues[N int64 | float64] struct {
noSum bool noSum bool
bounds []float64 bounds []float64
newRes func() exemplar.Reservoir[N]
limit limiter[*buckets[N]]
values map[attribute.Set]*buckets[N] values map[attribute.Set]*buckets[N]
valuesMu sync.Mutex valuesMu sync.Mutex
} }
func newHistValues[N int64 | float64](bounds []float64, noSum bool) *histValues[N] { func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() exemplar.Reservoir[N]) *histValues[N] {
// The responsibility of keeping all buckets correctly associated with the // The responsibility of keeping all buckets correctly associated with the
// passed boundaries is ultimately this type's responsibility. Make a copy // passed boundaries is ultimately this type's responsibility. Make a copy
// here so we can always guarantee this. Or, in the case of failure, have // here so we can always guarantee this. Or, in the case of failure, have
@ -69,13 +74,15 @@ func newHistValues[N int64 | float64](bounds []float64, noSum bool) *histValues[
return &histValues[N]{ return &histValues[N]{
noSum: noSum, noSum: noSum,
bounds: b, bounds: b,
newRes: r,
limit: newLimiter[*buckets[N]](limit),
values: make(map[attribute.Set]*buckets[N]), values: make(map[attribute.Set]*buckets[N]),
} }
} }
// Aggregate records the measurement value, scoped by attr, and aggregates it // Aggregate records the measurement value, scoped by attr, and aggregates it
// into a histogram. // into a histogram.
func (s *histValues[N]) measure(_ context.Context, value N, attr attribute.Set) { func (s *histValues[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) {
// This search will return an index in the range [0, len(s.bounds)], where // This search will return an index in the range [0, len(s.bounds)], where
// it will return len(s.bounds) if value is greater than the last element // it will return len(s.bounds) if value is greater than the last element
// of s.bounds. This aligns with the buckets in that the length of buckets // of s.bounds. This aligns with the buckets in that the length of buckets
@ -83,9 +90,12 @@ func (s *histValues[N]) measure(_ context.Context, value N, attr attribute.Set)
// (s.bounds[len(s.bounds)-1], +∞). // (s.bounds[len(s.bounds)-1], +∞).
idx := sort.SearchFloat64s(s.bounds, float64(value)) idx := sort.SearchFloat64s(s.bounds, float64(value))
t := now()
s.valuesMu.Lock() s.valuesMu.Lock()
defer s.valuesMu.Unlock() defer s.valuesMu.Unlock()
attr := s.limit.Attributes(fltrAttr, s.values)
b, ok := s.values[attr] b, ok := s.values[attr]
if !ok { if !ok {
// N+1 buckets. For example: // N+1 buckets. For example:
@ -96,6 +106,8 @@ func (s *histValues[N]) measure(_ context.Context, value N, attr attribute.Set)
// //
// buckets = (-∞, 0], (0, 5.0], (5.0, 10.0], (10.0, +∞) // buckets = (-∞, 0], (0, 5.0], (5.0, 10.0], (10.0, +∞)
b = newBuckets[N](len(s.bounds) + 1) b = newBuckets[N](len(s.bounds) + 1)
b.res = s.newRes()
// Ensure min and max are recorded values (not zero), for new buckets. // Ensure min and max are recorded values (not zero), for new buckets.
b.min, b.max = value, value b.min, b.max = value, value
s.values[attr] = b s.values[attr] = b
@ -104,13 +116,14 @@ func (s *histValues[N]) measure(_ context.Context, value N, attr attribute.Set)
if !s.noSum { if !s.noSum {
b.sum(value) b.sum(value)
} }
b.res.Offer(ctx, t, value, droppedAttr)
} }
// newHistogram returns an Aggregator that summarizes a set of measurements as // newHistogram returns an Aggregator that summarizes a set of measurements as
// an histogram. // an histogram.
func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool) *histogram[N] { func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir[N]) *histogram[N] {
return &histogram[N]{ return &histogram[N]{
histValues: newHistValues[N](boundaries, noSum), histValues: newHistValues[N](boundaries, noSum, limit, r),
noMinMax: noMinMax, noMinMax: noMinMax,
start: now(), start: now(),
} }
@ -161,6 +174,8 @@ func (s *histogram[N]) delta(dest *metricdata.Aggregation) int {
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(b.max)
} }
b.res.Collect(&hDPts[i].Exemplars)
// Unused attribute sets do not report. // Unused attribute sets do not report.
delete(s.values, a) delete(s.values, a)
i++ i++
@ -217,6 +232,9 @@ func (s *histogram[N]) cumulative(dest *metricdata.Aggregation) int {
hDPts[i].Min = metricdata.NewExtrema(b.min) hDPts[i].Min = metricdata.NewExtrema(b.min)
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(b.max)
} }
b.res.Collect(&hDPts[i].Exemplars)
i++ i++
// TODO (#3006): This will use an unbounded amount of memory if there // TODO (#3006): This will use an unbounded amount of memory if there
// are unbounded number of attribute sets being aggregated. Attribute // are unbounded number of attribute sets being aggregated. Attribute

View file

@ -20,6 +20,7 @@
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
@ -27,24 +28,43 @@
type datapoint[N int64 | float64] struct { type datapoint[N int64 | float64] struct {
timestamp time.Time timestamp time.Time
value N value N
res exemplar.Reservoir[N]
} }
func newLastValue[N int64 | float64]() *lastValue[N] { func newLastValue[N int64 | float64](limit int, r func() exemplar.Reservoir[N]) *lastValue[N] {
return &lastValue[N]{values: make(map[attribute.Set]datapoint[N])} return &lastValue[N]{
newRes: r,
limit: newLimiter[datapoint[N]](limit),
values: make(map[attribute.Set]datapoint[N]),
}
} }
// lastValue summarizes a set of measurements as the last one made. // lastValue summarizes a set of measurements as the last one made.
type lastValue[N int64 | float64] struct { type lastValue[N int64 | float64] struct {
sync.Mutex sync.Mutex
newRes func() exemplar.Reservoir[N]
limit limiter[datapoint[N]]
values map[attribute.Set]datapoint[N] values map[attribute.Set]datapoint[N]
} }
func (s *lastValue[N]) measure(ctx context.Context, value N, attr attribute.Set) { func (s *lastValue[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) {
d := datapoint[N]{timestamp: now(), value: value} t := now()
s.Lock() s.Lock()
defer s.Unlock()
attr := s.limit.Attributes(fltrAttr, s.values)
d, ok := s.values[attr]
if !ok {
d.res = s.newRes()
}
d.timestamp = t
d.value = value
d.res.Offer(ctx, t, value, droppedAttr)
s.values[attr] = d s.values[attr] = d
s.Unlock()
} }
func (s *lastValue[N]) computeAggregation(dest *[]metricdata.DataPoint[N]) { func (s *lastValue[N]) computeAggregation(dest *[]metricdata.DataPoint[N]) {
@ -61,6 +81,7 @@ func (s *lastValue[N]) computeAggregation(dest *[]metricdata.DataPoint[N]) {
// ignored. // ignored.
(*dest)[i].Time = v.timestamp (*dest)[i].Time = v.timestamp
(*dest)[i].Value = v.value (*dest)[i].Value = v.value
v.res.Collect(&(*dest)[i].Exemplars)
// Do not report stale values. // Do not report stale values.
delete(s.values, a) delete(s.values, a)
i++ i++

View file

@ -0,0 +1,53 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
import "go.opentelemetry.io/otel/attribute"
// overflowSet is the attribute set used to record a measurement when adding
// another distinct attribute set to the aggregate would exceed the aggregate
// limit.
var overflowSet = attribute.NewSet(attribute.Bool("otel.metric.overflow", true))
// limiter limits aggregate values.
type limiter[V any] struct {
// aggLimit is the maximum number of metric streams that can be aggregated.
//
// Any metric stream with attributes distinct from any set already
// aggregated once the aggLimit will be meet will instead be aggregated
// into an "overflow" metric stream. That stream will only contain the
// "otel.metric.overflow"=true attribute.
aggLimit int
}
// newLimiter returns a new Limiter with the provided aggregation limit.
func newLimiter[V any](aggregation int) limiter[V] {
return limiter[V]{aggLimit: aggregation}
}
// Attributes checks if adding a measurement for attrs will exceed the
// aggregation cardinality limit for the existing measurements. If it will,
// overflowSet is returned. Otherwise, if it will not exceed the limit, or the
// limit is not set (limit <= 0), attr is returned.
func (l limiter[V]) Attributes(attrs attribute.Set, measurements map[attribute.Set]V) attribute.Set {
if l.aggLimit > 0 {
_, exists := measurements[attrs]
if !exists && len(measurements) >= l.aggLimit-1 {
return overflowSet
}
}
return attrs
}

View file

@ -20,31 +20,55 @@
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
type sumValue[N int64 | float64] struct {
n N
res exemplar.Reservoir[N]
}
// valueMap is the storage for sums. // valueMap is the storage for sums.
type valueMap[N int64 | float64] struct { type valueMap[N int64 | float64] struct {
sync.Mutex sync.Mutex
values map[attribute.Set]N newRes func() exemplar.Reservoir[N]
limit limiter[sumValue[N]]
values map[attribute.Set]sumValue[N]
} }
func newValueMap[N int64 | float64]() *valueMap[N] { func newValueMap[N int64 | float64](limit int, r func() exemplar.Reservoir[N]) *valueMap[N] {
return &valueMap[N]{values: make(map[attribute.Set]N)} return &valueMap[N]{
newRes: r,
limit: newLimiter[sumValue[N]](limit),
values: make(map[attribute.Set]sumValue[N]),
}
} }
func (s *valueMap[N]) measure(_ context.Context, value N, attr attribute.Set) { func (s *valueMap[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) {
t := now()
s.Lock() s.Lock()
s.values[attr] += value defer s.Unlock()
s.Unlock()
attr := s.limit.Attributes(fltrAttr, s.values)
v, ok := s.values[attr]
if !ok {
v.res = s.newRes()
}
v.n += value
v.res.Offer(ctx, t, value, droppedAttr)
s.values[attr] = v
} }
// newSum returns an aggregator that summarizes a set of measurements as their // newSum returns an aggregator that summarizes a set of measurements as their
// arithmetic sum. Each sum is scoped by attributes and the aggregation cycle // arithmetic sum. Each sum is scoped by attributes and the aggregation cycle
// the measurements were made in. // the measurements were made in.
func newSum[N int64 | float64](monotonic bool) *sum[N] { func newSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.Reservoir[N]) *sum[N] {
return &sum[N]{ return &sum[N]{
valueMap: newValueMap[N](), valueMap: newValueMap[N](limit, r),
monotonic: monotonic, monotonic: monotonic,
start: now(), start: now(),
} }
@ -74,11 +98,12 @@ func (s *sum[N]) delta(dest *metricdata.Aggregation) int {
dPts := reset(sData.DataPoints, n, n) dPts := reset(sData.DataPoints, n, n)
var i int var i int
for attr, value := range s.values { for attr, val := range s.values {
dPts[i].Attributes = attr dPts[i].Attributes = attr
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = value dPts[i].Value = val.n
val.res.Collect(&dPts[i].Exemplars)
// Do not report stale values. // Do not report stale values.
delete(s.values, attr) delete(s.values, attr)
i++ i++
@ -112,7 +137,8 @@ func (s *sum[N]) cumulative(dest *metricdata.Aggregation) int {
dPts[i].Attributes = attr dPts[i].Attributes = attr
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = value dPts[i].Value = value.n
value.res.Collect(&dPts[i].Exemplars)
// TODO (#3006): This will use an unbounded amount of memory if there // TODO (#3006): This will use an unbounded amount of memory if there
// are unbounded number of attribute sets being aggregated. Attribute // are unbounded number of attribute sets being aggregated. Attribute
// sets that become "stale" need to be forgotten so this will not // sets that become "stale" need to be forgotten so this will not
@ -129,9 +155,9 @@ func (s *sum[N]) cumulative(dest *metricdata.Aggregation) int {
// newPrecomputedSum returns an aggregator that summarizes a set of // newPrecomputedSum returns an aggregator that summarizes a set of
// observatrions as their arithmetic sum. Each sum is scoped by attributes and // observatrions as their arithmetic sum. Each sum is scoped by attributes and
// the aggregation cycle the measurements were made in. // the aggregation cycle the measurements were made in.
func newPrecomputedSum[N int64 | float64](monotonic bool) *precomputedSum[N] { func newPrecomputedSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.Reservoir[N]) *precomputedSum[N] {
return &precomputedSum[N]{ return &precomputedSum[N]{
valueMap: newValueMap[N](), valueMap: newValueMap[N](limit, r),
monotonic: monotonic, monotonic: monotonic,
start: now(), start: now(),
} }
@ -165,14 +191,15 @@ func (s *precomputedSum[N]) delta(dest *metricdata.Aggregation) int {
var i int var i int
for attr, value := range s.values { for attr, value := range s.values {
delta := value - s.reported[attr] delta := value.n - s.reported[attr]
dPts[i].Attributes = attr dPts[i].Attributes = attr
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = delta dPts[i].Value = delta
value.res.Collect(&dPts[i].Exemplars)
newReported[attr] = value newReported[attr] = value.n
// Unused attribute sets do not report. // Unused attribute sets do not report.
delete(s.values, attr) delete(s.values, attr)
i++ i++
@ -204,11 +231,12 @@ func (s *precomputedSum[N]) cumulative(dest *metricdata.Aggregation) int {
dPts := reset(sData.DataPoints, n, n) dPts := reset(sData.DataPoints, n, n)
var i int var i int
for attr, value := range s.values { for attr, val := range s.values {
dPts[i].Attributes = attr dPts[i].Attributes = attr
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = value dPts[i].Value = val.n
val.res.Collect(&dPts[i].Exemplars)
// Unused attribute sets do not report. // Unused attribute sets do not report.
delete(s.values, attr) delete(s.values, attr)

View file

@ -0,0 +1,17 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package exemplar provides an implementation of the OpenTelemetry exemplar
// reservoir to be used in metric collection pipelines.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"

View file

@ -0,0 +1,36 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
// Drop returns a [Reservoir] that drops all measurements it is offered.
func Drop[N int64 | float64]() Reservoir[N] { return &dropRes[N]{} }
type dropRes[N int64 | float64] struct{}
// Offer does nothing, all measurements offered will be dropped.
func (r *dropRes[N]) Offer(context.Context, time.Time, N, []attribute.KeyValue) {}
// Collect resets dest. No exemplars will ever be returned.
func (r *dropRes[N]) Collect(dest *[]metricdata.Exemplar[N]) {
*dest = (*dest)[:0]
}

View file

@ -0,0 +1,40 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// SampledFilter returns a [Reservoir] wrapping r that will only offer measurements
// to r if the passed context associated with the measurement contains a sampled
// [go.opentelemetry.io/otel/trace.SpanContext].
func SampledFilter[N int64 | float64](r Reservoir[N]) Reservoir[N] {
return filtered[N]{Reservoir: r}
}
type filtered[N int64 | float64] struct {
Reservoir[N]
}
func (f filtered[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.KeyValue) {
if trace.SpanContextFromContext(ctx).IsSampled() {
f.Reservoir.Offer(ctx, t, n, a)
}
}

View file

@ -0,0 +1,47 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"sort"
"time"
"go.opentelemetry.io/otel/attribute"
)
// Histogram returns a [Reservoir] that samples the last measurement that falls
// within a histogram bucket. The histogram bucket upper-boundaries are define
// by bounds.
//
// The passed bounds will be sorted by this function.
func Histogram[N int64 | float64](bounds []float64) Reservoir[N] {
sort.Float64s(bounds)
return &histRes[N]{
bounds: bounds,
storage: newStorage[N](len(bounds) + 1),
}
}
type histRes[N int64 | float64] struct {
*storage[N]
// bounds are bucket bounds in ascending order.
bounds []float64
}
func (r *histRes[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.KeyValue) {
r.store[sort.SearchFloat64s(r.bounds, float64(n))] = newMeasurement(ctx, t, n, a)
}

View file

@ -0,0 +1,195 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"math"
"math/rand"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
// rng is used to make sampling decisions.
//
// Do not use crypto/rand. There is no reason for the decrease in performance
// given this is not a security sensitive decision.
var rng = rand.New(rand.NewSource(time.Now().UnixNano()))
// random returns, as a float64, a uniform pseudo-random number in the open
// interval (0.0,1.0).
func random() float64 {
// TODO: This does not return a uniform number. rng.Float64 returns a
// uniformly random int in [0,2^53) that is divided by 2^53. Meaning it
// returns multiples of 2^-53, and not all floating point numbers between 0
// and 1 (i.e. for values less than 2^-4 the 4 last bits of the significand
// are always going to be 0).
//
// An alternative algorithm should be considered that will actually return
// a uniform number in the interval (0,1). For example, since the default
// rand source provides a uniform distribution for Int63, this can be
// converted following the prototypical code of Mersenne Twister 64 (Takuji
// Nishimura and Makoto Matsumoto:
// http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/VERSIONS/C-LANG/mt19937-64.c)
//
// (float64(rng.Int63()>>11) + 0.5) * (1.0 / 4503599627370496.0)
//
// There are likely many other methods to explore here as well.
f := rng.Float64()
for f == 0 {
f = rng.Float64()
}
return f
}
// FixedSize returns a [Reservoir] that samples at most k exemplars. If there
// are k or less measurements made, the Reservoir will sample each one. If
// there are more than k, the Reservoir will then randomly sample all
// additional measurement with a decreasing probability.
func FixedSize[N int64 | float64](k int) Reservoir[N] {
r := &randRes[N]{storage: newStorage[N](k)}
r.reset()
return r
}
type randRes[N int64 | float64] struct {
*storage[N]
// count is the number of measurement seen.
count int64
// next is the next count that will store a measurement at a random index
// once the reservoir has been filled.
next int64
// w is the largest random number in a distribution that is used to compute
// the next next.
w float64
}
func (r *randRes[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.KeyValue) {
// The following algorithm is "Algorithm L" from Li, Kim-Hung (4 December
// 1994). "Reservoir-Sampling Algorithms of Time Complexity
// O(n(1+log(N/n)))". ACM Transactions on Mathematical Software. 20 (4):
// 481493 (https://dl.acm.org/doi/10.1145/198429.198435).
//
// A high-level overview of "Algorithm L":
// 0) Pre-calculate the random count greater than the storage size when
// an exemplar will be replaced.
// 1) Accept all measurements offered until the configured storage size is
// reached.
// 2) Loop:
// a) When the pre-calculate count is reached, replace a random
// existing exemplar with the offered measurement.
// b) Calculate the next random count greater than the existing one
// which will replace another exemplars
//
// The way a "replacement" count is computed is by looking at `n` number of
// independent random numbers each corresponding to an offered measurement.
// Of these numbers the smallest `k` (the same size as the storage
// capacity) of them are kept as a subset. The maximum value in this
// subset, called `w` is used to weight another random number generation
// for the next count that will be considered.
//
// By weighting the next count computation like described, it is able to
// perform a uniformly-weighted sampling algorithm based on the number of
// samples the reservoir has seen so far. The sampling will "slow down" as
// more and more samples are offered so as to reduce a bias towards those
// offered just prior to the end of the collection.
//
// This algorithm is preferred because of its balance of simplicity and
// performance. It will compute three random numbers (the bulk of
// computation time) for each item that becomes part of the reservoir, but
// it does not spend any time on items that do not. In particular it has an
// asymptotic runtime of O(k(1 + log(n/k)) where n is the number of
// measurements offered and k is the reservoir size.
//
// See https://en.wikipedia.org/wiki/Reservoir_sampling for an overview of
// this and other reservoir sampling algorithms. See
// https://github.com/MrAlias/reservoir-sampling for a performance
// comparison of reservoir sampling algorithms.
if int(r.count) < cap(r.store) {
r.store[r.count] = newMeasurement(ctx, t, n, a)
} else {
if r.count == r.next {
// Overwrite a random existing measurement with the one offered.
idx := int(rng.Int63n(int64(cap(r.store))))
r.store[idx] = newMeasurement(ctx, t, n, a)
r.advance()
}
}
r.count++
}
// reset resets r to the initial state.
func (r *randRes[N]) reset() {
// This resets the number of exemplars known.
r.count = 0
// Random index inserts should only happen after the storage is full.
r.next = int64(cap(r.store))
// Initial random number in the series used to generate r.next.
//
// This is set before r.advance to reset or initialize the random number
// series. Without doing so it would always be 0 or never restart a new
// random number series.
//
// This maps the uniform random number in (0,1) to a geometric distribution
// over the same interval. The mean of the distribution is inversely
// proportional to the storage capacity.
r.w = math.Exp(math.Log(random()) / float64(cap(r.store)))
r.advance()
}
// advance updates the count at which the offered measurement will overwrite an
// existing exemplar.
func (r *randRes[N]) advance() {
// Calculate the next value in the random number series.
//
// The current value of r.w is based on the max of a distribution of random
// numbers (i.e. `w = max(u_1,u_2,...,u_k)` for `k` equal to the capacity
// of the storage and each `u` in the interval (0,w)). To calculate the
// next r.w we use the fact that when the next exemplar is selected to be
// included in the storage an existing one will be dropped, and the
// corresponding random number in the set used to calculate r.w will also
// be replaced. The replacement random number will also be within (0,w),
// therefore the next r.w will be based on the same distribution (i.e.
// `max(u_1,u_2,...,u_k)`). Therefore, we can sample the next r.w by
// computing the next random number `u` and take r.w as `w * u^(1/k)`.
r.w *= math.Exp(math.Log(random()) / float64(cap(r.store)))
// Use the new random number in the series to calculate the count of the
// next measurement that will be stored.
//
// Given 0 < r.w < 1, each iteration will result in subsequent r.w being
// smaller. This translates here into the next next being selected against
// a distribution with a higher mean (i.e. the expected value will increase
// and replacements become less likely)
//
// Important to note, the new r.next will always be at least 1 more than
// the last r.next.
r.next += int64(math.Log(random())/math.Log(1-r.w)) + 1
}
func (r *randRes[N]) Collect(dest *[]metricdata.Exemplar[N]) {
r.storage.Collect(dest)
// Call reset here even though it will reset r.count and restart the random
// number series. This will persist any old exemplars as long as no new
// measurements are offered, but it will also prioritize those new
// measurements that are made over the older collection cycle ones.
r.reset()
}

View file

@ -0,0 +1,44 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
// Reservoir holds the sampled exemplar of measurements made.
type Reservoir[N int64 | float64] interface {
// Offer accepts the parameters associated with a measurement. The
// parameters will be stored as an exemplar if the Reservoir decides to
// sample the measurement.
//
// The passed ctx needs to contain any baggage or span that were active
// when the measurement was made. This information may be used by the
// Reservoir in making a sampling decision.
//
// The time t is the time when the measurement was made. The val and attr
// parameters are the value and dropped (filtered) attributes of the
// measurement respectively.
Offer(ctx context.Context, t time.Time, val N, attr []attribute.KeyValue)
// Collect returns all the held exemplars.
//
// The Reservoir state is preserved after this call.
Collect(dest *[]metricdata.Exemplar[N])
}

View file

@ -0,0 +1,107 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/trace"
)
// storage is an exemplar storage for [Reservoir] implementations.
type storage[N int64 | float64] struct {
// store are the measurements sampled.
//
// This does not use []metricdata.Exemplar because it potentially would
// require an allocation for trace and span IDs in the hot path of Offer.
store []measurement[N]
}
func newStorage[N int64 | float64](n int) *storage[N] {
return &storage[N]{store: make([]measurement[N], n)}
}
// Collect returns all the held exemplars.
//
// The Reservoir state is preserved after this call.
func (r *storage[N]) Collect(dest *[]metricdata.Exemplar[N]) {
*dest = reset(*dest, len(r.store), len(r.store))
var n int
for _, m := range r.store {
if !m.valid {
continue
}
m.Exemplar(&(*dest)[n])
n++
}
*dest = (*dest)[:n]
}
// measurement is a measurement made by a telemetry system.
type measurement[N int64 | float64] struct {
// FilteredAttributes are the attributes dropped during the measurement.
FilteredAttributes []attribute.KeyValue
// Time is the time when the measurement was made.
Time time.Time
// Value is the value of the measurement.
Value N
// SpanContext is the SpanContext active when a measurement was made.
SpanContext trace.SpanContext
valid bool
}
// newMeasurement returns a new non-empty Measurement.
func newMeasurement[N int64 | float64](ctx context.Context, ts time.Time, v N, droppedAttr []attribute.KeyValue) measurement[N] {
return measurement[N]{
FilteredAttributes: droppedAttr,
Time: ts,
Value: v,
SpanContext: trace.SpanContextFromContext(ctx),
valid: true,
}
}
// Exemplar returns m as a [metricdata.Exemplar].
func (m measurement[N]) Exemplar(dest *metricdata.Exemplar[N]) {
dest.FilteredAttributes = m.FilteredAttributes
dest.Time = m.Time
dest.Value = m.Value
if m.SpanContext.HasTraceID() {
traceID := m.SpanContext.TraceID()
dest.TraceID = traceID[:]
} else {
dest.TraceID = dest.TraceID[:0]
}
if m.SpanContext.HasSpanID() {
spanID := m.SpanContext.SpanID()
dest.SpanID = spanID[:]
} else {
dest.SpanID = dest.SpanID[:0]
}
}
func reset[T any](s []T, length, capacity int) []T {
if cap(s) < capacity {
return make([]T, length, capacity)
}
return s[:length]
}

View file

@ -0,0 +1,112 @@
# Experimental Features
The metric SDK contains features that have not yet stabilized in the OpenTelemetry specification.
These features are added to the OpenTelemetry Go metric SDK prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
These feature may change in backwards incompatible ways as feedback is applied.
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
## Features
- [Cardinality Limit](#cardinality-limit)
- [Exemplars](#exemplars)
### Cardinality Limit
The cardinality limit is the hard limit on the number of metric streams that can be collected for a single instrument.
This experimental feature can be enabled by setting the `OTEL_GO_X_CARDINALITY_LIMIT` environment value.
The value must be an integer value.
All other values are ignored.
If the value set is less than or equal to `0`, no limit will be applied.
#### Examples
Set the cardinality limit to 2000.
```console
export OTEL_GO_X_CARDINALITY_LIMIT=2000
```
Set an infinite cardinality limit (functionally equivalent to disabling the feature).
```console
export OTEL_GO_X_CARDINALITY_LIMIT=-1
```
Disable the cardinality limit.
```console
unset OTEL_GO_X_CARDINALITY_LIMIT
```
### Exemplars
A sample of measurements made may be exported directly as a set of exemplars.
This experimental feature can be enabled by setting the `OTEL_GO_X_EXEMPLAR` environment variable.
The value of must be the case-insensitive string of `"true"` to enable the feature.
All other values are ignored.
Exemplar filters are a supported.
The exemplar filter applies to all measurements made.
They filter these measurements, only allowing certain measurements to be passed to the underlying exemplar reservoir.
To change the exemplar filter from the default `"trace_based"` filter set the `OTEL_METRICS_EXEMPLAR_FILTER` environment variable.
The value must be the case-sensitive string defined by the [OpenTelemetry specification].
- `"always_on"`: allows all measurements
- `"always_off"`: denies all measurements
- `"trace_based"`: allows only sampled measurements
All values other than these will result in the default, `"trace_based"`, exemplar filter being used.
[OpenTelemetry specification]: https://github.com/open-telemetry/opentelemetry-specification/blob/a6ca2fd484c9e76fe1d8e1c79c99f08f4745b5ee/specification/configuration/sdk-environment-variables.md#exemplar
#### Examples
Enable exemplars to be exported.
```console
export OTEL_GO_X_EXEMPLAR=true
```
Disable exemplars from being exported.
```console
unset OTEL_GO_X_EXEMPLAR
```
Set the exemplar filter to allow all measurements.
```console
export OTEL_METRICS_EXEMPLAR_FILTER=always_on
```
Set the exemplar filter to deny all measurements.
```console
export OTEL_METRICS_EXEMPLAR_FILTER=always_off
```
Set the exemplar filter to only allow sampled measurements.
```console
export OTEL_METRICS_EXEMPLAR_FILTER=trace_based
```
Revert to the default exemplar filter (`"trace_based"`)
```console
unset OTEL_METRICS_EXEMPLAR_FILTER
```
## Compatibility and Stability
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md).
These features may be removed or modified in successive version releases, including patch versions.
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.

View file

@ -0,0 +1,96 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package x contains support for OTel metric SDK experimental features.
//
// This package should only be used for features defined in the specification.
// It should not be used for experiments or new project ideas.
package x // import "go.opentelemetry.io/otel/sdk/metric/internal/x"
import (
"os"
"strconv"
"strings"
)
var (
// Exemplars is an experimental feature flag that defines if exemplars
// should be recorded for metric data-points.
//
// To enable this feature set the OTEL_GO_X_EXEMPLAR environment variable
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
// will also enable this).
Exemplars = newFeature("EXEMPLAR", func(v string) (string, bool) {
if strings.ToLower(v) == "true" {
return v, true
}
return "", false
})
// CardinalityLimit is an experimental feature flag that defines if
// cardinality limits should be applied to the recorded metric data-points.
//
// To enable this feature set the OTEL_GO_X_CARDINALITY_LIMIT environment
// variable to the integer limit value you want to use.
//
// Setting OTEL_GO_X_CARDINALITY_LIMIT to a value less than or equal to 0
// will disable the cardinality limits.
CardinalityLimit = newFeature("CARDINALITY_LIMIT", func(v string) (int, bool) {
n, err := strconv.Atoi(v)
if err != nil {
return 0, false
}
return n, true
})
)
// Feature is an experimental feature control flag. It provides a uniform way
// to interact with these feature flags and parse their values.
type Feature[T any] struct {
key string
parse func(v string) (T, bool)
}
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
const envKeyRoot = "OTEL_GO_X_"
return Feature[T]{
key: envKeyRoot + suffix,
parse: parse,
}
}
// Key returns the environment variable key that needs to be set to enable the
// feature.
func (f Feature[T]) Key() string { return f.key }
// Lookup returns the user configured value for the feature and true if the
// user has enabled the feature. Otherwise, if the feature is not enabled, a
// zero-value and false are returned.
func (f Feature[T]) Lookup() (v T, ok bool) {
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
//
// > The SDK MUST interpret an empty value of an environment variable the
// > same way as when the variable is unset.
vRaw := os.Getenv(f.key)
if vRaw == "" {
return v, ok
}
return f.parse(vRaw)
}
// Enabled returns if the feature is enabled.
func (f Feature[T]) Enabled() bool {
_, ok := f.Lookup()
return ok
}

View file

@ -23,6 +23,7 @@
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/embedded"
"go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/internal/aggregate" "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
) )
@ -40,6 +41,11 @@ type meter struct {
scope instrumentation.Scope scope instrumentation.Scope
pipes pipelines pipes pipelines
int64Insts *cacheWithErr[instID, *int64Inst]
float64Insts *cacheWithErr[instID, *float64Inst]
int64ObservableInsts *cacheWithErr[instID, int64Observable]
float64ObservableInsts *cacheWithErr[instID, float64Observable]
int64Resolver resolver[int64] int64Resolver resolver[int64]
float64Resolver resolver[float64] float64Resolver resolver[float64]
} }
@ -49,11 +55,20 @@ func newMeter(s instrumentation.Scope, p pipelines) *meter {
// meter is asked to create are logged to the user. // meter is asked to create are logged to the user.
var viewCache cache[string, instID] var viewCache cache[string, instID]
var int64Insts cacheWithErr[instID, *int64Inst]
var float64Insts cacheWithErr[instID, *float64Inst]
var int64ObservableInsts cacheWithErr[instID, int64Observable]
var float64ObservableInsts cacheWithErr[instID, float64Observable]
return &meter{ return &meter{
scope: s, scope: s,
pipes: p, pipes: p,
int64Resolver: newResolver[int64](p, &viewCache), int64Insts: &int64Insts,
float64Resolver: newResolver[float64](p, &viewCache), float64Insts: &float64Insts,
int64ObservableInsts: &int64ObservableInsts,
float64ObservableInsts: &float64ObservableInsts,
int64Resolver: newResolver[int64](p, &viewCache),
float64Resolver: newResolver[float64](p, &viewCache),
} }
} }
@ -104,20 +119,62 @@ func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOpti
return i, validateInstrumentName(name) return i, validateInstrumentName(name)
} }
// int64ObservableInstrument returns a new observable identified by the Instrument.
// It registers callbacks for each reader's pipeline.
func (m *meter) int64ObservableInstrument(id Instrument, callbacks []metric.Int64Callback) (int64Observable, error) {
key := instID{
Name: id.Name,
Description: id.Description,
Unit: id.Unit,
Kind: id.Kind,
}
if m.int64ObservableInsts.HasKey(key) && len(callbacks) > 0 {
warnRepeatedObservableCallbacks(id)
}
return m.int64ObservableInsts.Lookup(key, func() (int64Observable, error) {
inst := newInt64Observable(m, id.Kind, id.Name, id.Description, id.Unit)
for _, insert := range m.int64Resolver.inserters {
// Connect the measure functions for instruments in this pipeline with the
// callbacks for this pipeline.
in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind))
if err != nil {
return inst, err
}
// Drop aggregation
if len(in) == 0 {
inst.dropAggregation = true
continue
}
inst.appendMeasures(in)
for _, cback := range callbacks {
inst := int64Observer{measures: in}
fn := cback
insert.addCallback(func(ctx context.Context) error { return fn(ctx, inst) })
}
}
return inst, validateInstrumentName(id.Name)
})
}
// Int64ObservableCounter returns a new instrument identified by name and // Int64ObservableCounter returns a new instrument identified by name and
// configured with options. The instrument is used to asynchronously record // configured with options. The instrument is used to asynchronously record
// increasing int64 measurements once per a measurement collection cycle. // increasing int64 measurements once per a measurement collection cycle.
// Only the measurements recorded during the collection cycle are exported. // Only the measurements recorded during the collection cycle are exported.
//
// If Int64ObservableCounter is invoked repeatedly with the same Name,
// Description, and Unit, only the first set of callbacks provided are used.
// Use meter.RegisterCallback and Registration.Unregister to manage callbacks
// if instrumentation can be created multiple times with different callbacks.
func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) {
cfg := metric.NewInt64ObservableCounterConfig(options...) cfg := metric.NewInt64ObservableCounterConfig(options...)
const kind = InstrumentKindObservableCounter id := Instrument{
p := int64ObservProvider{m} Name: name,
inst, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) Description: cfg.Description(),
if err != nil { Unit: cfg.Unit(),
return nil, err Kind: InstrumentKindObservableCounter,
Scope: m.scope,
} }
p.registerCallbacks(inst, cfg.Callbacks()) return m.int64ObservableInstrument(id, cfg.Callbacks())
return inst, validateInstrumentName(name)
} }
// Int64ObservableUpDownCounter returns a new instrument identified by name and // Int64ObservableUpDownCounter returns a new instrument identified by name and
@ -126,14 +183,14 @@ func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64Obser
// measurements recorded during the collection cycle are exported. // measurements recorded during the collection cycle are exported.
func (m *meter) Int64ObservableUpDownCounter(name string, options ...metric.Int64ObservableUpDownCounterOption) (metric.Int64ObservableUpDownCounter, error) { func (m *meter) Int64ObservableUpDownCounter(name string, options ...metric.Int64ObservableUpDownCounterOption) (metric.Int64ObservableUpDownCounter, error) {
cfg := metric.NewInt64ObservableUpDownCounterConfig(options...) cfg := metric.NewInt64ObservableUpDownCounterConfig(options...)
const kind = InstrumentKindObservableUpDownCounter id := Instrument{
p := int64ObservProvider{m} Name: name,
inst, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) Description: cfg.Description(),
if err != nil { Unit: cfg.Unit(),
return nil, err Kind: InstrumentKindObservableUpDownCounter,
Scope: m.scope,
} }
p.registerCallbacks(inst, cfg.Callbacks()) return m.int64ObservableInstrument(id, cfg.Callbacks())
return inst, validateInstrumentName(name)
} }
// Int64ObservableGauge returns a new instrument identified by name and // Int64ObservableGauge returns a new instrument identified by name and
@ -142,14 +199,14 @@ func (m *meter) Int64ObservableUpDownCounter(name string, options ...metric.Int6
// Only the measurements recorded during the collection cycle are exported. // Only the measurements recorded during the collection cycle are exported.
func (m *meter) Int64ObservableGauge(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { func (m *meter) Int64ObservableGauge(name string, options ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) {
cfg := metric.NewInt64ObservableGaugeConfig(options...) cfg := metric.NewInt64ObservableGaugeConfig(options...)
const kind = InstrumentKindObservableGauge id := Instrument{
p := int64ObservProvider{m} Name: name,
inst, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) Description: cfg.Description(),
if err != nil { Unit: cfg.Unit(),
return nil, err Kind: InstrumentKindObservableGauge,
Scope: m.scope,
} }
p.registerCallbacks(inst, cfg.Callbacks()) return m.int64ObservableInstrument(id, cfg.Callbacks())
return inst, validateInstrumentName(name)
} }
// Float64Counter returns a new instrument identified by name and configured // Float64Counter returns a new instrument identified by name and configured
@ -196,20 +253,62 @@ func (m *meter) Float64Histogram(name string, options ...metric.Float64Histogram
return i, validateInstrumentName(name) return i, validateInstrumentName(name)
} }
// float64ObservableInstrument returns a new observable identified by the Instrument.
// It registers callbacks for each reader's pipeline.
func (m *meter) float64ObservableInstrument(id Instrument, callbacks []metric.Float64Callback) (float64Observable, error) {
key := instID{
Name: id.Name,
Description: id.Description,
Unit: id.Unit,
Kind: id.Kind,
}
if m.int64ObservableInsts.HasKey(key) && len(callbacks) > 0 {
warnRepeatedObservableCallbacks(id)
}
return m.float64ObservableInsts.Lookup(key, func() (float64Observable, error) {
inst := newFloat64Observable(m, id.Kind, id.Name, id.Description, id.Unit)
for _, insert := range m.float64Resolver.inserters {
// Connect the measure functions for instruments in this pipeline with the
// callbacks for this pipeline.
in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind))
if err != nil {
return inst, err
}
// Drop aggregation
if len(in) == 0 {
inst.dropAggregation = true
continue
}
inst.appendMeasures(in)
for _, cback := range callbacks {
inst := float64Observer{measures: in}
fn := cback
insert.addCallback(func(ctx context.Context) error { return fn(ctx, inst) })
}
}
return inst, validateInstrumentName(id.Name)
})
}
// Float64ObservableCounter returns a new instrument identified by name and // Float64ObservableCounter returns a new instrument identified by name and
// configured with options. The instrument is used to asynchronously record // configured with options. The instrument is used to asynchronously record
// increasing float64 measurements once per a measurement collection cycle. // increasing float64 measurements once per a measurement collection cycle.
// Only the measurements recorded during the collection cycle are exported. // Only the measurements recorded during the collection cycle are exported.
//
// If Float64ObservableCounter is invoked repeatedly with the same Name,
// Description, and Unit, only the first set of callbacks provided are used.
// Use meter.RegisterCallback and Registration.Unregister to manage callbacks
// if instrumentation can be created multiple times with different callbacks.
func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) {
cfg := metric.NewFloat64ObservableCounterConfig(options...) cfg := metric.NewFloat64ObservableCounterConfig(options...)
const kind = InstrumentKindObservableCounter id := Instrument{
p := float64ObservProvider{m} Name: name,
inst, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) Description: cfg.Description(),
if err != nil { Unit: cfg.Unit(),
return nil, err Kind: InstrumentKindObservableCounter,
Scope: m.scope,
} }
p.registerCallbacks(inst, cfg.Callbacks()) return m.float64ObservableInstrument(id, cfg.Callbacks())
return inst, validateInstrumentName(name)
} }
// Float64ObservableUpDownCounter returns a new instrument identified by name // Float64ObservableUpDownCounter returns a new instrument identified by name
@ -218,14 +317,14 @@ func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64O
// measurements recorded during the collection cycle are exported. // measurements recorded during the collection cycle are exported.
func (m *meter) Float64ObservableUpDownCounter(name string, options ...metric.Float64ObservableUpDownCounterOption) (metric.Float64ObservableUpDownCounter, error) { func (m *meter) Float64ObservableUpDownCounter(name string, options ...metric.Float64ObservableUpDownCounterOption) (metric.Float64ObservableUpDownCounter, error) {
cfg := metric.NewFloat64ObservableUpDownCounterConfig(options...) cfg := metric.NewFloat64ObservableUpDownCounterConfig(options...)
const kind = InstrumentKindObservableUpDownCounter id := Instrument{
p := float64ObservProvider{m} Name: name,
inst, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) Description: cfg.Description(),
if err != nil { Unit: cfg.Unit(),
return nil, err Kind: InstrumentKindObservableUpDownCounter,
Scope: m.scope,
} }
p.registerCallbacks(inst, cfg.Callbacks()) return m.float64ObservableInstrument(id, cfg.Callbacks())
return inst, validateInstrumentName(name)
} }
// Float64ObservableGauge returns a new instrument identified by name and // Float64ObservableGauge returns a new instrument identified by name and
@ -234,14 +333,14 @@ func (m *meter) Float64ObservableUpDownCounter(name string, options ...metric.Fl
// Only the measurements recorded during the collection cycle are exported. // Only the measurements recorded during the collection cycle are exported.
func (m *meter) Float64ObservableGauge(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { func (m *meter) Float64ObservableGauge(name string, options ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) {
cfg := metric.NewFloat64ObservableGaugeConfig(options...) cfg := metric.NewFloat64ObservableGaugeConfig(options...)
const kind = InstrumentKindObservableGauge id := Instrument{
p := float64ObservProvider{m} Name: name,
inst, err := p.lookup(kind, name, cfg.Description(), cfg.Unit()) Description: cfg.Description(),
if err != nil { Unit: cfg.Unit(),
return nil, err Kind: InstrumentKindObservableGauge,
Scope: m.scope,
} }
p.registerCallbacks(inst, cfg.Callbacks()) return m.float64ObservableInstrument(id, cfg.Callbacks())
return inst, validateInstrumentName(name)
} }
func validateInstrumentName(name string) error { func validateInstrumentName(name string) error {
@ -273,6 +372,16 @@ func isAlphanumeric(c rune) bool {
return isAlpha(c) || ('0' <= c && c <= '9') return isAlpha(c) || ('0' <= c && c <= '9')
} }
func warnRepeatedObservableCallbacks(id Instrument) {
inst := fmt.Sprintf(
"Instrument{Name: %q, Description: %q, Kind: %q, Unit: %q}",
id.Name, id.Description, "InstrumentKind"+id.Kind.String(), id.Unit,
)
global.Warn("Repeated observable instrument creation with callbacks. Ignoring new callbacks. Use meter.RegisterCallback and Registration.Unregister to manage callbacks.",
"instrument", inst,
)
}
// RegisterCallback registers f to be called each collection cycle so it will // RegisterCallback registers f to be called each collection cycle so it will
// make observations for insts during those cycles. // make observations for insts during those cycles.
// //
@ -389,12 +498,14 @@ func (r observer) ObserveFloat64(o metric.Float64Observable, v float64, opts ...
} }
if _, registered := r.float64[oImpl.observablID]; !registered { if _, registered := r.float64[oImpl.observablID]; !registered {
global.Error(errUnregObserver, "failed to record", if !oImpl.dropAggregation {
"name", oImpl.name, global.Error(errUnregObserver, "failed to record",
"description", oImpl.description, "name", oImpl.name,
"unit", oImpl.unit, "description", oImpl.description,
"number", fmt.Sprintf("%T", float64(0)), "unit", oImpl.unit,
) "number", fmt.Sprintf("%T", float64(0)),
)
}
return return
} }
c := metric.NewObserveConfig(opts) c := metric.NewObserveConfig(opts)
@ -422,12 +533,14 @@ func (r observer) ObserveInt64(o metric.Int64Observable, v int64, opts ...metric
} }
if _, registered := r.int64[oImpl.observablID]; !registered { if _, registered := r.int64[oImpl.observablID]; !registered {
global.Error(errUnregObserver, "failed to record", if !oImpl.dropAggregation {
"name", oImpl.name, global.Error(errUnregObserver, "failed to record",
"description", oImpl.description, "name", oImpl.name,
"unit", oImpl.unit, "description", oImpl.description,
"number", fmt.Sprintf("%T", int64(0)), "unit", oImpl.unit,
) "number", fmt.Sprintf("%T", int64(0)),
)
}
return return
} }
c := metric.NewObserveConfig(opts) c := metric.NewObserveConfig(opts)
@ -474,14 +587,28 @@ func (p int64InstProvider) histogramAggs(name string, cfg metric.Int64HistogramC
// lookup returns the resolved instrumentImpl. // lookup returns the resolved instrumentImpl.
func (p int64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*int64Inst, error) { func (p int64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*int64Inst, error) {
aggs, err := p.aggs(kind, name, desc, u) return p.meter.int64Insts.Lookup(instID{
return &int64Inst{measures: aggs}, err Name: name,
Description: desc,
Unit: u,
Kind: kind,
}, func() (*int64Inst, error) {
aggs, err := p.aggs(kind, name, desc, u)
return &int64Inst{measures: aggs}, err
})
} }
// lookupHistogram returns the resolved instrumentImpl. // lookupHistogram returns the resolved instrumentImpl.
func (p int64InstProvider) lookupHistogram(name string, cfg metric.Int64HistogramConfig) (*int64Inst, error) { func (p int64InstProvider) lookupHistogram(name string, cfg metric.Int64HistogramConfig) (*int64Inst, error) {
aggs, err := p.histogramAggs(name, cfg) return p.meter.int64Insts.Lookup(instID{
return &int64Inst{measures: aggs}, err Name: name,
Description: cfg.Description(),
Unit: cfg.Unit(),
Kind: InstrumentKindHistogram,
}, func() (*int64Inst, error) {
aggs, err := p.histogramAggs(name, cfg)
return &int64Inst{measures: aggs}, err
})
} }
// float64InstProvider provides float64 OpenTelemetry instruments. // float64InstProvider provides float64 OpenTelemetry instruments.
@ -518,42 +645,33 @@ func (p float64InstProvider) histogramAggs(name string, cfg metric.Float64Histog
// lookup returns the resolved instrumentImpl. // lookup returns the resolved instrumentImpl.
func (p float64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*float64Inst, error) { func (p float64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*float64Inst, error) {
aggs, err := p.aggs(kind, name, desc, u) return p.meter.float64Insts.Lookup(instID{
return &float64Inst{measures: aggs}, err Name: name,
Description: desc,
Unit: u,
Kind: kind,
}, func() (*float64Inst, error) {
aggs, err := p.aggs(kind, name, desc, u)
return &float64Inst{measures: aggs}, err
})
} }
// lookupHistogram returns the resolved instrumentImpl. // lookupHistogram returns the resolved instrumentImpl.
func (p float64InstProvider) lookupHistogram(name string, cfg metric.Float64HistogramConfig) (*float64Inst, error) { func (p float64InstProvider) lookupHistogram(name string, cfg metric.Float64HistogramConfig) (*float64Inst, error) {
aggs, err := p.histogramAggs(name, cfg) return p.meter.float64Insts.Lookup(instID{
return &float64Inst{measures: aggs}, err Name: name,
} Description: cfg.Description(),
Unit: cfg.Unit(),
type int64ObservProvider struct{ *meter } Kind: InstrumentKindHistogram,
}, func() (*float64Inst, error) {
func (p int64ObservProvider) lookup(kind InstrumentKind, name, desc, u string) (int64Observable, error) { aggs, err := p.histogramAggs(name, cfg)
aggs, err := (int64InstProvider)(p).aggs(kind, name, desc, u) return &float64Inst{measures: aggs}, err
return newInt64Observable(p.meter, kind, name, desc, u, aggs), err })
}
func (p int64ObservProvider) registerCallbacks(inst int64Observable, cBacks []metric.Int64Callback) {
if inst.observable == nil || len(inst.measures) == 0 {
// Drop aggregator.
return
}
for _, cBack := range cBacks {
p.pipes.registerCallback(p.callback(inst, cBack))
}
}
func (p int64ObservProvider) callback(i int64Observable, f metric.Int64Callback) func(context.Context) error {
inst := int64Observer{int64Observable: i}
return func(ctx context.Context) error { return f(ctx, inst) }
} }
type int64Observer struct { type int64Observer struct {
embedded.Int64Observer embedded.Int64Observer
int64Observable measures[int64]
} }
func (o int64Observer) Observe(val int64, opts ...metric.ObserveOption) { func (o int64Observer) Observe(val int64, opts ...metric.ObserveOption) {
@ -561,32 +679,9 @@ func (o int64Observer) Observe(val int64, opts ...metric.ObserveOption) {
o.observe(val, c.Attributes()) o.observe(val, c.Attributes())
} }
type float64ObservProvider struct{ *meter }
func (p float64ObservProvider) lookup(kind InstrumentKind, name, desc, u string) (float64Observable, error) {
aggs, err := (float64InstProvider)(p).aggs(kind, name, desc, u)
return newFloat64Observable(p.meter, kind, name, desc, u, aggs), err
}
func (p float64ObservProvider) registerCallbacks(inst float64Observable, cBacks []metric.Float64Callback) {
if inst.observable == nil || len(inst.measures) == 0 {
// Drop aggregator.
return
}
for _, cBack := range cBacks {
p.pipes.registerCallback(p.callback(inst, cBack))
}
}
func (p float64ObservProvider) callback(i float64Observable, f metric.Float64Callback) func(context.Context) error {
inst := float64Observer{float64Observable: i}
return func(ctx context.Context) error { return f(ctx, inst) }
}
type float64Observer struct { type float64Observer struct {
embedded.Float64Observer embedded.Float64Observer
float64Observable measures[float64]
} }
func (o float64Observer) Observe(val float64, opts ...metric.ObserveOption) { func (o float64Observer) Observe(val float64, opts ...metric.ObserveOption) {

View file

@ -15,6 +15,7 @@
package metricdata // import "go.opentelemetry.io/otel/sdk/metric/metricdata" package metricdata // import "go.opentelemetry.io/otel/sdk/metric/metricdata"
import ( import (
"encoding/json"
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
@ -211,6 +212,19 @@ type Extrema[N int64 | float64] struct {
valid bool valid bool
} }
// MarshalText converts the Extrema value to text.
func (e Extrema[N]) MarshalText() ([]byte, error) {
if !e.valid {
return json.Marshal(nil)
}
return json.Marshal(e.value)
}
// MarshalJSON converts the Extrema value to JSON number.
func (e *Extrema[N]) MarshalJSON() ([]byte, error) {
return e.MarshalText()
}
// NewExtrema returns an Extrema set to v. // NewExtrema returns an Extrema set to v.
func NewExtrema[N int64 | float64](v N) Extrema[N] { func NewExtrema[N int64 | float64](v N) Extrema[N] {
return Extrema[N]{value: v, valid: true} return Extrema[N]{value: v, valid: true}

View file

@ -29,6 +29,7 @@
"go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/internal" "go.opentelemetry.io/otel/sdk/metric/internal"
"go.opentelemetry.io/otel/sdk/metric/internal/aggregate" "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
"go.opentelemetry.io/otel/sdk/metric/internal/x"
"go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
) )
@ -93,14 +94,6 @@ func (p *pipeline) addSync(scope instrumentation.Scope, iSync instrumentSync) {
p.aggregations[scope] = append(p.aggregations[scope], iSync) p.aggregations[scope] = append(p.aggregations[scope], iSync)
} }
// addCallback registers a single instrument callback to be run when
// `produce()` is called.
func (p *pipeline) addCallback(cback func(context.Context) error) {
p.Lock()
defer p.Unlock()
p.callbacks = append(p.callbacks, cback)
}
type multiCallback func(context.Context) error type multiCallback func(context.Context) error
// addMultiCallback registers a multi-instrument callback to be run when // addMultiCallback registers a multi-instrument callback to be run when
@ -281,6 +274,14 @@ func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation)
return measures, errs.errorOrNil() return measures, errs.errorOrNil()
} }
// addCallback registers a single instrument callback to be run when
// `produce()` is called.
func (i *inserter[N]) addCallback(cback func(context.Context) error) {
i.pipeline.Lock()
defer i.pipeline.Unlock()
i.pipeline.callbacks = append(i.pipeline.callbacks, cback)
}
var aggIDCount uint64 var aggIDCount uint64
// aggVal is the cached value in an aggregators cache. // aggVal is the cached value in an aggregators cache.
@ -358,9 +359,16 @@ func (i *inserter[N]) cachedAggregator(scope instrumentation.Scope, kind Instrum
normID := id.normalize() normID := id.normalize()
cv := i.aggregators.Lookup(normID, func() aggVal[N] { cv := i.aggregators.Lookup(normID, func() aggVal[N] {
b := aggregate.Builder[N]{ b := aggregate.Builder[N]{
Temporality: i.pipeline.reader.temporality(kind), Temporality: i.pipeline.reader.temporality(kind),
ReservoirFunc: reservoirFunc[N](stream.Aggregation),
} }
b.Filter = stream.AttributeFilter b.Filter = stream.AttributeFilter
// A value less than or equal to zero will disable the aggregation
// limits for the builder (an all the created aggregates).
// CardinalityLimit.Lookup returns 0 by default if unset (or
// unrecognized input). Use that value directly.
b.AggregationLimit, _ = x.CardinalityLimit.Lookup()
in, out, err := i.aggregateFunc(b, stream.Aggregation, kind) in, out, err := i.aggregateFunc(b, stream.Aggregation, kind)
if err != nil { if err != nil {
return aggVal[N]{0, nil, err} return aggVal[N]{0, nil, err}
@ -557,12 +565,6 @@ func newPipelines(res *resource.Resource, readers []Reader, views []View) pipeli
return pipes return pipes
} }
func (p pipelines) registerCallback(cback func(context.Context) error) {
for _, pipe := range p {
pipe.addCallback(cback)
}
}
func (p pipelines) registerMultiCallback(c multiCallback) metric.Registration { func (p pipelines) registerMultiCallback(c multiCallback) metric.Registration {
unregs := make([]func(), len(p)) unregs := make([]func(), len(p))
for i, pipe := range p { for i, pipe := range p {

View file

@ -16,5 +16,5 @@
// version is the current release version of the metric SDK in use. // version is the current release version of the metric SDK in use.
func version() string { func version() string {
return "1.20.0" return "1.24.0"
} }

View file

@ -41,8 +41,20 @@ type Detector interface {
// must never be done outside of a new major release. // must never be done outside of a new major release.
} }
// Detect calls all input detectors sequentially and merges each result with the previous one. // Detect returns a new [Resource] merged from all the Resources each of the
// It returns the merged error too. // detectors produces. Each of the detectors are called sequentially, in the
// order they are passed, merging the produced resource into the previous.
//
// This may return a partial Resource along with an error containing
// [ErrPartialResource] if that error is returned from a detector. It may also
// return a merge-conflicting Resource along with an error containing
// [ErrSchemaURLConflict] if merging Resources from different detectors results
// in a schema URL conflict. It is up to the caller to determine if this
// returned Resource should be used or not.
//
// If one of the detectors returns an error that is not [ErrPartialResource],
// the resource produced by the detector will not be merged and the returned
// error will wrap that detector's error.
func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) { func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
r := new(Resource) r := new(Resource)
return r, detect(ctx, r, detectors) return r, detect(ctx, r, detectors)
@ -50,6 +62,10 @@ func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
// detect runs all detectors using ctx and merges the result into res. This // detect runs all detectors using ctx and merges the result into res. This
// assumes res is allocated and not nil, it will panic otherwise. // assumes res is allocated and not nil, it will panic otherwise.
//
// If the detectors or merging resources produces any errors (i.e.
// [ErrPartialResource] [ErrSchemaURLConflict]), a single error wrapping all of
// these errors will be returned. Otherwise, nil is returned.
func detect(ctx context.Context, res *Resource, detectors []Detector) error { func detect(ctx context.Context, res *Resource, detectors []Detector) error {
var ( var (
r *Resource r *Resource
@ -78,6 +94,11 @@ func detect(ctx context.Context, res *Resource, detectors []Detector) error {
if len(errs) == 0 { if len(errs) == 0 {
return nil return nil
} }
if errors.Is(errs, ErrSchemaURLConflict) {
// If there has been a merge conflict, ensure the resource has no
// schema URL.
res.schemaURL = ""
}
return errs return errs
} }

View file

@ -22,7 +22,7 @@
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk" "go.opentelemetry.io/otel/sdk"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
type ( type (

View file

@ -22,14 +22,14 @@
"os" "os"
"regexp" "regexp"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
type containerIDProvider func() (string, error) type containerIDProvider func() (string, error)
var ( var (
containerID containerIDProvider = getContainerIDFromCGroup containerID containerIDProvider = getContainerIDFromCGroup
cgroupContainerIDRe = regexp.MustCompile(`^.*/(?:.*-)?([0-9a-f]+)(?:\.|\s*$)`) cgroupContainerIDRe = regexp.MustCompile(`^.*/(?:.*[-:])?([0-9a-f]+)(?:\.|\s*$)`)
) )
type cgroupContainerIDDetector struct{} type cgroupContainerIDDetector struct{}

View file

@ -23,7 +23,7 @@
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
const ( const (

View file

@ -19,7 +19,7 @@
"errors" "errors"
"strings" "strings"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
type hostIDProvider func() (string, error) type hostIDProvider func() (string, error)

View file

@ -19,7 +19,7 @@
"strings" "strings"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
type osDescriptionProvider func() (string, error) type osDescriptionProvider func() (string, error)

View file

@ -22,7 +22,7 @@
"path/filepath" "path/filepath"
"runtime" "runtime"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
) )
type ( type (

View file

@ -17,6 +17,7 @@
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"sync" "sync"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
@ -40,9 +41,20 @@ type Resource struct {
defaultResourceOnce sync.Once defaultResourceOnce sync.Once
) )
var errMergeConflictSchemaURL = errors.New("cannot merge resource due to conflicting Schema URL") // ErrSchemaURLConflict is an error returned when two Resources are merged
// together that contain different, non-empty, schema URLs.
var ErrSchemaURLConflict = errors.New("conflicting Schema URL")
// New returns a Resource combined from the user-provided detectors. // New returns a [Resource] built using opts.
//
// This may return a partial Resource along with an error containing
// [ErrPartialResource] if options that provide a [Detector] are used and that
// error is returned from one or more of the Detectors. It may also return a
// merge-conflict Resource along with an error containing
// [ErrSchemaURLConflict] if merging Resources from the opts results in a
// schema URL conflict (see [Resource.Merge] for more information). It is up to
// the caller to determine if this returned Resource should be used or not
// based on these errors.
func New(ctx context.Context, opts ...Option) (*Resource, error) { func New(ctx context.Context, opts ...Option) (*Resource, error) {
cfg := config{} cfg := config{}
for _, opt := range opts { for _, opt := range opts {
@ -98,7 +110,7 @@ func (r *Resource) String() string {
return r.attrs.Encoded(attribute.DefaultEncoder()) return r.attrs.Encoded(attribute.DefaultEncoder())
} }
// MarshalLog is the marshaling function used by the logging system to represent this exporter. // MarshalLog is the marshaling function used by the logging system to represent this Resource.
func (r *Resource) MarshalLog() interface{} { func (r *Resource) MarshalLog() interface{} {
return struct { return struct {
Attributes attribute.Set Attributes attribute.Set
@ -146,16 +158,29 @@ func (r *Resource) Equal(eq *Resource) bool {
return r.Equivalent() == eq.Equivalent() return r.Equivalent() == eq.Equivalent()
} }
// Merge creates a new resource by combining resource a and b. // Merge creates a new [Resource] by merging a and b.
// //
// If there are common keys between resource a and b, then the value // If there are common keys between a and b, then the value from b will
// from resource b will overwrite the value from resource a, even // overwrite the value from a, even if b's value is empty.
// if resource b's value is empty.
// //
// The SchemaURL of the resources will be merged according to the spec rules: // The SchemaURL of the resources will be merged according to the
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge // [OpenTelemetry specification rules]:
// If the resources have different non-empty schemaURL an empty resource and an error //
// will be returned. // - If a's schema URL is empty then the returned Resource's schema URL will
// be set to the schema URL of b,
// - Else if b's schema URL is empty then the returned Resource's schema URL
// will be set to the schema URL of a,
// - Else if the schema URLs of a and b are the same then that will be the
// schema URL of the returned Resource,
// - Else this is a merging error. If the resources have different,
// non-empty, schema URLs an error containing [ErrSchemaURLConflict] will
// be returned with the merged Resource. The merged Resource will have an
// empty schema URL. It may be the case that some unintended attributes
// have been overwritten or old semantic conventions persisted in the
// returned Resource. It is up to the caller to determine if this returned
// Resource should be used or not.
//
// [OpenTelemetry specification rules]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge
func Merge(a, b *Resource) (*Resource, error) { func Merge(a, b *Resource) (*Resource, error) {
if a == nil && b == nil { if a == nil && b == nil {
return Empty(), nil return Empty(), nil
@ -167,19 +192,6 @@ func Merge(a, b *Resource) (*Resource, error) {
return a, nil return a, nil
} }
// Merge the schema URL.
var schemaURL string
switch true {
case a.schemaURL == "":
schemaURL = b.schemaURL
case b.schemaURL == "":
schemaURL = a.schemaURL
case a.schemaURL == b.schemaURL:
schemaURL = a.schemaURL
default:
return Empty(), errMergeConflictSchemaURL
}
// Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key()
// Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...)
mi := attribute.NewMergeIterator(b.Set(), a.Set()) mi := attribute.NewMergeIterator(b.Set(), a.Set())
@ -187,8 +199,23 @@ func Merge(a, b *Resource) (*Resource, error) {
for mi.Next() { for mi.Next() {
combine = append(combine, mi.Attribute()) combine = append(combine, mi.Attribute())
} }
merged := NewWithAttributes(schemaURL, combine...)
return merged, nil switch {
case a.schemaURL == "":
return NewWithAttributes(b.schemaURL, combine...), nil
case b.schemaURL == "":
return NewWithAttributes(a.schemaURL, combine...), nil
case a.schemaURL == b.schemaURL:
return NewWithAttributes(a.schemaURL, combine...), nil
}
// Return the merged resource with an appropriate error. It is up to
// the user to decide if the returned resource can be used or not.
return NewSchemaless(combine...), fmt.Errorf(
"%w: %s and %s",
ErrSchemaURLConflict,
a.schemaURL,
b.schemaURL,
)
} }
// Empty returns an instance of Resource with no attributes. It is // Empty returns an instance of Resource with no attributes. It is

View file

@ -406,7 +406,7 @@ func (bsp *batchSpanProcessor) enqueueDrop(ctx context.Context, sd ReadOnlySpan)
return false return false
} }
// MarshalLog is the marshaling function used by the logging system to represent this exporter. // MarshalLog is the marshaling function used by the logging system to represent this Span Processor.
func (bsp *batchSpanProcessor) MarshalLog() interface{} { func (bsp *batchSpanProcessor) MarshalLog() interface{} {
return struct { return struct {
Type string Type string

View file

@ -55,7 +55,7 @@ type tracerProviderConfig struct {
resource *resource.Resource resource *resource.Resource
} }
// MarshalLog is the marshaling function used by the logging system to represent this exporter. // MarshalLog is the marshaling function used by the logging system to represent this Provider.
func (cfg tracerProviderConfig) MarshalLog() interface{} { func (cfg tracerProviderConfig) MarshalLog() interface{} {
return struct { return struct {
SpanProcessors []SpanProcessor SpanProcessors []SpanProcessor

View file

@ -30,7 +30,7 @@
"go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/internal" "go.opentelemetry.io/otel/sdk/internal"
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded" "go.opentelemetry.io/otel/trace/embedded"
) )
@ -208,6 +208,16 @@ func (s *recordingSpan) SetStatus(code codes.Code, description string) {
s.status = status s.status = status
} }
// ensureAttributesCapacity inlines functionality from slices.Grow
// so that we can avoid needing to import golang.org/x/exp for go1.20.
// Once support for go1.20 is dropped, we can use slices.Grow available since go1.21 instead.
// Tracking issue: https://github.com/open-telemetry/opentelemetry-go/issues/4819.
func (s *recordingSpan) ensureAttributesCapacity(minCapacity int) {
if n := minCapacity - cap(s.attributes); n > 0 {
s.attributes = append(s.attributes[:cap(s.attributes)], make([]attribute.KeyValue, n)...)[:len(s.attributes)]
}
}
// SetAttributes sets attributes of this span. // SetAttributes sets attributes of this span.
// //
// If a key from attributes already exists the value associated with that key // If a key from attributes already exists the value associated with that key
@ -242,6 +252,7 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
// Otherwise, add without deduplication. When attributes are read they // Otherwise, add without deduplication. When attributes are read they
// will be deduplicated, optimizing the operation. // will be deduplicated, optimizing the operation.
s.ensureAttributesCapacity(len(s.attributes) + len(attributes))
for _, a := range attributes { for _, a := range attributes {
if !a.Valid() { if !a.Valid() {
// Drop all invalid attributes. // Drop all invalid attributes.
@ -277,6 +288,12 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
// Now that s.attributes is deduplicated, adding unique attributes up to // Now that s.attributes is deduplicated, adding unique attributes up to
// the capacity of s will not over allocate s.attributes. // the capacity of s will not over allocate s.attributes.
if sum := len(attrs) + len(s.attributes); sum < limit {
// After support for go1.20 is dropped, simplify if-else to min(sum, limit).
s.ensureAttributesCapacity(sum)
} else {
s.ensureAttributesCapacity(limit)
}
for _, a := range attrs { for _, a := range attrs {
if !a.Valid() { if !a.Valid() {
// Drop all invalid attributes. // Drop all invalid attributes.

View file

@ -16,5 +16,5 @@
// Version is the current release version of the OpenTelemetry SDK in use. // Version is the current release version of the OpenTelemetry SDK in use.
func Version() string { func Version() string {
return "1.20.0" return "1.24.0"
} }

View file

@ -15,6 +15,6 @@
// Package semconv implements OpenTelemetry semantic conventions. // Package semconv implements OpenTelemetry semantic conventions.
// //
// OpenTelemetry semantic conventions are agreed standardized naming // OpenTelemetry semantic conventions are agreed standardized naming
// patterns for OpenTelemetry things. This package represents the conventions // patterns for OpenTelemetry things. This package represents the v1.21.0
// as of the v1.21.0 version of the OpenTelemetry specification. // version of the OpenTelemetry semantic conventions.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.21.0" package semconv // import "go.opentelemetry.io/otel/semconv/v1.21.0"

File diff suppressed because it is too large Load diff

20
vendor/go.opentelemetry.io/otel/semconv/v1.24.0/doc.go generated vendored Normal file
View file

@ -0,0 +1,20 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package semconv implements OpenTelemetry semantic conventions.
//
// OpenTelemetry semantic conventions are agreed standardized naming
// patterns for OpenTelemetry things. This package represents the v1.24.0
// version of the OpenTelemetry semantic conventions.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.24.0"

View file

@ -0,0 +1,211 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated from semantic convention specification. DO NOT EDIT.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.24.0"
import "go.opentelemetry.io/otel/attribute"
// This event represents an occurrence of a lifecycle transition on the iOS
// platform.
const (
// IosStateKey is the attribute Key conforming to the "ios.state" semantic
// conventions. It represents the this attribute represents the state the
// application has transitioned into at the occurrence of the event.
//
// Type: Enum
// RequirementLevel: Required
// Stability: experimental
// Note: The iOS lifecycle states are defined in the [UIApplicationDelegate
// documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902),
// and from which the `OS terminology` column values are derived.
IosStateKey = attribute.Key("ios.state")
)
var (
// The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`
IosStateActive = IosStateKey.String("active")
// The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`
IosStateInactive = IosStateKey.String("inactive")
// The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`
IosStateBackground = IosStateKey.String("background")
// The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`
IosStateForeground = IosStateKey.String("foreground")
// The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`
IosStateTerminate = IosStateKey.String("terminate")
)
// This event represents an occurrence of a lifecycle transition on the Android
// platform.
const (
// AndroidStateKey is the attribute Key conforming to the "android.state"
// semantic conventions. It represents the this attribute represents the
// state the application has transitioned into at the occurrence of the
// event.
//
// Type: Enum
// RequirementLevel: Required
// Stability: experimental
// Note: The Android lifecycle states are defined in [Activity lifecycle
// callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc),
// and from which the `OS identifiers` are derived.
AndroidStateKey = attribute.Key("android.state")
)
var (
// Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time
AndroidStateCreated = AndroidStateKey.String("created")
// Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state
AndroidStateBackground = AndroidStateKey.String("background")
// Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states
AndroidStateForeground = AndroidStateKey.String("foreground")
)
// This semantic convention defines the attributes used to represent a feature
// flag evaluation as an event.
const (
// FeatureFlagKeyKey is the attribute Key conforming to the
// "feature_flag.key" semantic conventions. It represents the unique
// identifier of the feature flag.
//
// Type: string
// RequirementLevel: Required
// Stability: experimental
// Examples: 'logo-color'
FeatureFlagKeyKey = attribute.Key("feature_flag.key")
// FeatureFlagProviderNameKey is the attribute Key conforming to the
// "feature_flag.provider_name" semantic conventions. It represents the
// name of the service provider that performs the flag evaluation.
//
// Type: string
// RequirementLevel: Recommended
// Stability: experimental
// Examples: 'Flag Manager'
FeatureFlagProviderNameKey = attribute.Key("feature_flag.provider_name")
// FeatureFlagVariantKey is the attribute Key conforming to the
// "feature_flag.variant" semantic conventions. It represents the sHOULD be
// a semantic identifier for a value. If one is unavailable, a stringified
// version of the value can be used.
//
// Type: string
// RequirementLevel: Recommended
// Stability: experimental
// Examples: 'red', 'true', 'on'
// Note: A semantic identifier, commonly referred to as a variant, provides
// a means
// for referring to a value without including the value itself. This can
// provide additional context for understanding the meaning behind a value.
// For example, the variant `red` maybe be used for the value `#c05543`.
//
// A stringified version of the value can be used in situations where a
// semantic identifier is unavailable. String representation of the value
// should be determined by the implementer.
FeatureFlagVariantKey = attribute.Key("feature_flag.variant")
)
// FeatureFlagKey returns an attribute KeyValue conforming to the
// "feature_flag.key" semantic conventions. It represents the unique identifier
// of the feature flag.
func FeatureFlagKey(val string) attribute.KeyValue {
return FeatureFlagKeyKey.String(val)
}
// FeatureFlagProviderName returns an attribute KeyValue conforming to the
// "feature_flag.provider_name" semantic conventions. It represents the name of
// the service provider that performs the flag evaluation.
func FeatureFlagProviderName(val string) attribute.KeyValue {
return FeatureFlagProviderNameKey.String(val)
}
// FeatureFlagVariant returns an attribute KeyValue conforming to the
// "feature_flag.variant" semantic conventions. It represents the sHOULD be a
// semantic identifier for a value. If one is unavailable, a stringified
// version of the value can be used.
func FeatureFlagVariant(val string) attribute.KeyValue {
return FeatureFlagVariantKey.String(val)
}
// RPC received/sent message.
const (
// MessageCompressedSizeKey is the attribute Key conforming to the
// "message.compressed_size" semantic conventions. It represents the
// compressed size of the message in bytes.
//
// Type: int
// RequirementLevel: Optional
// Stability: experimental
MessageCompressedSizeKey = attribute.Key("message.compressed_size")
// MessageIDKey is the attribute Key conforming to the "message.id"
// semantic conventions. It represents the mUST be calculated as two
// different counters starting from `1` one for sent messages and one for
// received message.
//
// Type: int
// RequirementLevel: Optional
// Stability: experimental
// Note: This way we guarantee that the values will be consistent between
// different implementations.
MessageIDKey = attribute.Key("message.id")
// MessageTypeKey is the attribute Key conforming to the "message.type"
// semantic conventions. It represents the whether this is a received or
// sent message.
//
// Type: Enum
// RequirementLevel: Optional
// Stability: experimental
MessageTypeKey = attribute.Key("message.type")
// MessageUncompressedSizeKey is the attribute Key conforming to the
// "message.uncompressed_size" semantic conventions. It represents the
// uncompressed size of the message in bytes.
//
// Type: int
// RequirementLevel: Optional
// Stability: experimental
MessageUncompressedSizeKey = attribute.Key("message.uncompressed_size")
)
var (
// sent
MessageTypeSent = MessageTypeKey.String("SENT")
// received
MessageTypeReceived = MessageTypeKey.String("RECEIVED")
)
// MessageCompressedSize returns an attribute KeyValue conforming to the
// "message.compressed_size" semantic conventions. It represents the compressed
// size of the message in bytes.
func MessageCompressedSize(val int) attribute.KeyValue {
return MessageCompressedSizeKey.Int(val)
}
// MessageID returns an attribute KeyValue conforming to the "message.id"
// semantic conventions. It represents the mUST be calculated as two different
// counters starting from `1` one for sent messages and one for received
// message.
func MessageID(val int) attribute.KeyValue {
return MessageIDKey.Int(val)
}
// MessageUncompressedSize returns an attribute KeyValue conforming to the
// "message.uncompressed_size" semantic conventions. It represents the
// uncompressed size of the message in bytes.
func MessageUncompressedSize(val int) attribute.KeyValue {
return MessageUncompressedSizeKey.Int(val)
}

View file

@ -0,0 +1,20 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.24.0"
const (
// ExceptionEventName is the name of the Span event representing an exception.
ExceptionEventName = "exception"
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.24.0"
// SchemaURL is the schema URL that matches the version of the semantic conventions
// that this package defines. Semconv packages starting from v1.4.0 must declare
// non-empty schema URL in the form https://opentelemetry.io/schemas/<version>
const SchemaURL = "https://opentelemetry.io/schemas/1.24.0"

1334
vendor/go.opentelemetry.io/otel/semconv/v1.24.0/trace.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -17,20 +17,14 @@
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp"
"strings" "strings"
) )
const ( const (
maxListMembers = 32 maxListMembers = 32
listDelimiter = "," listDelimiters = ","
memberDelimiter = "="
// based on the W3C Trace Context specification, see
// https://www.w3.org/TR/trace-context-1/#tracestate-header
noTenantKeyFormat = `[a-z][_0-9a-z\-\*\/]*`
withTenantKeyFormat = `[a-z0-9][_0-9a-z\-\*\/]*@[a-z][_0-9a-z\-\*\/]*`
valueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]*[\x21-\x2b\x2d-\x3c\x3e-\x7e]`
errInvalidKey errorConst = "invalid tracestate key" errInvalidKey errorConst = "invalid tracestate key"
errInvalidValue errorConst = "invalid tracestate value" errInvalidValue errorConst = "invalid tracestate value"
@ -39,43 +33,128 @@
errDuplicate errorConst = "duplicate list-member in tracestate" errDuplicate errorConst = "duplicate list-member in tracestate"
) )
var (
noTenantKeyRe = regexp.MustCompile(`^` + noTenantKeyFormat + `$`)
withTenantKeyRe = regexp.MustCompile(`^` + withTenantKeyFormat + `$`)
valueRe = regexp.MustCompile(`^` + valueFormat + `$`)
memberRe = regexp.MustCompile(`^\s*((?:` + noTenantKeyFormat + `)|(?:` + withTenantKeyFormat + `))=(` + valueFormat + `)\s*$`)
)
type member struct { type member struct {
Key string Key string
Value string Value string
} }
// according to (chr = %x20 / (nblk-char = %x21-2B / %x2D-3C / %x3E-7E) )
// means (chr = %x20-2B / %x2D-3C / %x3E-7E) .
func checkValueChar(v byte) bool {
return v >= '\x20' && v <= '\x7e' && v != '\x2c' && v != '\x3d'
}
// according to (nblk-chr = %x21-2B / %x2D-3C / %x3E-7E) .
func checkValueLast(v byte) bool {
return v >= '\x21' && v <= '\x7e' && v != '\x2c' && v != '\x3d'
}
// based on the W3C Trace Context specification
//
// value = (0*255(chr)) nblk-chr
// nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
// chr = %x20 / nblk-chr
//
// see https://www.w3.org/TR/trace-context-1/#value
func checkValue(val string) bool {
n := len(val)
if n == 0 || n > 256 {
return false
}
for i := 0; i < n-1; i++ {
if !checkValueChar(val[i]) {
return false
}
}
return checkValueLast(val[n-1])
}
func checkKeyRemain(key string) bool {
// ( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
for _, v := range key {
if isAlphaNum(byte(v)) {
continue
}
switch v {
case '_', '-', '*', '/':
continue
}
return false
}
return true
}
// according to
//
// simple-key = lcalpha (0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
// system-id = lcalpha (0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
//
// param n is remain part length, should be 255 in simple-key or 13 in system-id.
func checkKeyPart(key string, n int) bool {
if len(key) == 0 {
return false
}
first := key[0] // key's first char
ret := len(key[1:]) <= n
ret = ret && first >= 'a' && first <= 'z'
return ret && checkKeyRemain(key[1:])
}
func isAlphaNum(c byte) bool {
if c >= 'a' && c <= 'z' {
return true
}
return c >= '0' && c <= '9'
}
// according to
//
// tenant-id = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
//
// param n is remain part length, should be 240 exactly.
func checkKeyTenant(key string, n int) bool {
if len(key) == 0 {
return false
}
return isAlphaNum(key[0]) && len(key[1:]) <= n && checkKeyRemain(key[1:])
}
// based on the W3C Trace Context specification
//
// key = simple-key / multi-tenant-key
// simple-key = lcalpha (0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
// multi-tenant-key = tenant-id "@" system-id
// tenant-id = ( lcalpha / DIGIT ) (0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
// system-id = lcalpha (0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ))
// lcalpha = %x61-7A ; a-z
//
// see https://www.w3.org/TR/trace-context-1/#tracestate-header.
func checkKey(key string) bool {
tenant, system, ok := strings.Cut(key, "@")
if !ok {
return checkKeyPart(key, 255)
}
return checkKeyTenant(tenant, 240) && checkKeyPart(system, 13)
}
func newMember(key, value string) (member, error) { func newMember(key, value string) (member, error) {
if len(key) > 256 { if !checkKey(key) {
return member{}, fmt.Errorf("%w: %s", errInvalidKey, key) return member{}, errInvalidKey
} }
if !noTenantKeyRe.MatchString(key) { if !checkValue(value) {
if !withTenantKeyRe.MatchString(key) { return member{}, errInvalidValue
return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
}
atIndex := strings.LastIndex(key, "@")
if atIndex > 241 || len(key)-1-atIndex > 14 {
return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
}
}
if len(value) > 256 || !valueRe.MatchString(value) {
return member{}, fmt.Errorf("%w: %s", errInvalidValue, value)
} }
return member{Key: key, Value: value}, nil return member{Key: key, Value: value}, nil
} }
func parseMember(m string) (member, error) { func parseMember(m string) (member, error) {
matches := memberRe.FindStringSubmatch(m) key, val, ok := strings.Cut(m, memberDelimiter)
if len(matches) != 3 { if !ok {
return member{}, fmt.Errorf("%w: %s", errInvalidMember, m) return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
} }
result, e := newMember(matches[1], matches[2]) key = strings.TrimLeft(key, " \t")
val = strings.TrimRight(val, " \t")
result, e := newMember(key, val)
if e != nil { if e != nil {
return member{}, fmt.Errorf("%w: %s", errInvalidMember, m) return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
} }
@ -85,7 +164,7 @@ func parseMember(m string) (member, error) {
// String encodes member into a string compliant with the W3C Trace Context // String encodes member into a string compliant with the W3C Trace Context
// specification. // specification.
func (m member) String() string { func (m member) String() string {
return fmt.Sprintf("%s=%s", m.Key, m.Value) return m.Key + "=" + m.Value
} }
// TraceState provides additional vendor-specific trace identification // TraceState provides additional vendor-specific trace identification
@ -109,8 +188,8 @@ type TraceState struct {
// ParseTraceState attempts to decode a TraceState from the passed // ParseTraceState attempts to decode a TraceState from the passed
// string. It returns an error if the input is invalid according to the W3C // string. It returns an error if the input is invalid according to the W3C
// Trace Context specification. // Trace Context specification.
func ParseTraceState(tracestate string) (TraceState, error) { func ParseTraceState(ts string) (TraceState, error) {
if tracestate == "" { if ts == "" {
return TraceState{}, nil return TraceState{}, nil
} }
@ -120,7 +199,9 @@ func ParseTraceState(tracestate string) (TraceState, error) {
var members []member var members []member
found := make(map[string]struct{}) found := make(map[string]struct{})
for _, memberStr := range strings.Split(tracestate, listDelimiter) { for ts != "" {
var memberStr string
memberStr, ts, _ = strings.Cut(ts, listDelimiters)
if len(memberStr) == 0 { if len(memberStr) == 0 {
continue continue
} }
@ -153,11 +234,29 @@ func (ts TraceState) MarshalJSON() ([]byte, error) {
// Trace Context specification. The returned string will be invalid if the // Trace Context specification. The returned string will be invalid if the
// TraceState contains any invalid members. // TraceState contains any invalid members.
func (ts TraceState) String() string { func (ts TraceState) String() string {
members := make([]string, len(ts.list)) if len(ts.list) == 0 {
for i, m := range ts.list { return ""
members[i] = m.String()
} }
return strings.Join(members, listDelimiter) var n int
n += len(ts.list) // member delimiters: '='
n += len(ts.list) - 1 // list delimiters: ','
for _, mem := range ts.list {
n += len(mem.Key)
n += len(mem.Value)
}
var sb strings.Builder
sb.Grow(n)
_, _ = sb.WriteString(ts.list[0].Key)
_ = sb.WriteByte('=')
_, _ = sb.WriteString(ts.list[0].Value)
for i := 1; i < len(ts.list); i++ {
_ = sb.WriteByte(listDelimiters[0])
_, _ = sb.WriteString(ts.list[i].Key)
_ = sb.WriteByte('=')
_, _ = sb.WriteString(ts.list[i].Value)
}
return sb.String()
} }
// Get returns the value paired with key from the corresponding TraceState // Get returns the value paired with key from the corresponding TraceState
@ -189,15 +288,25 @@ func (ts TraceState) Insert(key, value string) (TraceState, error) {
if err != nil { if err != nil {
return ts, err return ts, err
} }
n := len(ts.list)
cTS := ts.Delete(key) found := n
if cTS.Len()+1 <= maxListMembers { for i := range ts.list {
cTS.list = append(cTS.list, member{}) if ts.list[i].Key == key {
found = i
}
}
cTS := TraceState{}
if found == n && n < maxListMembers {
cTS.list = make([]member, n+1)
} else {
cTS.list = make([]member, n)
} }
// When the number of members exceeds capacity, drop the "right-most".
copy(cTS.list[1:], cTS.list)
cTS.list[0] = m cTS.list[0] = m
// When the number of members exceeds capacity, drop the "right-most".
copy(cTS.list[1:], ts.list[0:found])
if found < n {
copy(cTS.list[1+found:], ts.list[found+1:])
}
return cTS, nil return cTS, nil
} }

View file

@ -16,5 +16,5 @@
// Version is the current release version of OpenTelemetry in use. // Version is the current release version of OpenTelemetry in use.
func Version() string { func Version() string {
return "1.20.0" return "1.24.0"
} }

View file

@ -14,20 +14,25 @@
module-sets: module-sets:
stable-v1: stable-v1:
version: v1.20.0 version: v1.24.0
modules: modules:
- go.opentelemetry.io/otel - go.opentelemetry.io/otel
- go.opentelemetry.io/otel/bridge/opencensus
- go.opentelemetry.io/otel/bridge/opencensus/test
- go.opentelemetry.io/otel/bridge/opentracing - go.opentelemetry.io/otel/bridge/opentracing
- go.opentelemetry.io/otel/bridge/opentracing/test - go.opentelemetry.io/otel/bridge/opentracing/test
- go.opentelemetry.io/otel/example/dice - go.opentelemetry.io/otel/example/dice
- go.opentelemetry.io/otel/example/fib
- go.opentelemetry.io/otel/example/namedtracer - go.opentelemetry.io/otel/example/namedtracer
- go.opentelemetry.io/otel/example/opencensus
- go.opentelemetry.io/otel/example/otel-collector - go.opentelemetry.io/otel/example/otel-collector
- go.opentelemetry.io/otel/example/passthrough - go.opentelemetry.io/otel/example/passthrough
- go.opentelemetry.io/otel/example/zipkin - go.opentelemetry.io/otel/example/zipkin
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
- go.opentelemetry.io/otel/exporters/otlp/otlptrace - go.opentelemetry.io/otel/exporters/otlp/otlptrace
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
- go.opentelemetry.io/otel/exporters/stdout/stdoutmetric
- go.opentelemetry.io/otel/exporters/stdout/stdouttrace - go.opentelemetry.io/otel/exporters/stdout/stdouttrace
- go.opentelemetry.io/otel/exporters/zipkin - go.opentelemetry.io/otel/exporters/zipkin
- go.opentelemetry.io/otel/metric - go.opentelemetry.io/otel/metric
@ -35,18 +40,14 @@ module-sets:
- go.opentelemetry.io/otel/sdk/metric - go.opentelemetry.io/otel/sdk/metric
- go.opentelemetry.io/otel/trace - go.opentelemetry.io/otel/trace
experimental-metrics: experimental-metrics:
version: v0.43.0 version: v0.46.0
modules: modules:
- go.opentelemetry.io/otel/bridge/opencensus
- go.opentelemetry.io/otel/bridge/opencensus/test
- go.opentelemetry.io/otel/example/opencensus
- go.opentelemetry.io/otel/example/prometheus - go.opentelemetry.io/otel/example/prometheus
- go.opentelemetry.io/otel/example/view
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
- go.opentelemetry.io/otel/exporters/prometheus - go.opentelemetry.io/otel/exporters/prometheus
- go.opentelemetry.io/otel/exporters/stdout/stdoutmetric experimental-logs:
version: v0.0.1-alpha
modules:
- go.opentelemetry.io/otel/log
experimental-schema: experimental-schema:
version: v0.0.7 version: v0.0.7
modules: modules:

View file

@ -36,6 +36,69 @@
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
// SpanFlags represents constants used to interpret the
// Span.flags field, which is protobuf 'fixed32' type and is to
// be used as bit-fields. Each non-zero value defined in this enum is
// a bit-mask. To extract the bit-field, for example, use an
// expression like:
//
// (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK)
//
// See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions.
//
// Note that Span flags were introduced in version 1.1 of the
// OpenTelemetry protocol. Older Span producers do not set this
// field, consequently consumers should not rely on the absence of a
// particular flag bit to indicate the presence of a particular feature.
type SpanFlags int32
const (
// The zero value for the enum. Should not be used for comparisons.
// Instead use bitwise "and" with the appropriate mask as shown above.
SpanFlags_SPAN_FLAGS_DO_NOT_USE SpanFlags = 0
// Bits 0-7 are used for trace flags.
SpanFlags_SPAN_FLAGS_TRACE_FLAGS_MASK SpanFlags = 255
)
// Enum value maps for SpanFlags.
var (
SpanFlags_name = map[int32]string{
0: "SPAN_FLAGS_DO_NOT_USE",
255: "SPAN_FLAGS_TRACE_FLAGS_MASK",
}
SpanFlags_value = map[string]int32{
"SPAN_FLAGS_DO_NOT_USE": 0,
"SPAN_FLAGS_TRACE_FLAGS_MASK": 255,
}
)
func (x SpanFlags) Enum() *SpanFlags {
p := new(SpanFlags)
*p = x
return p
}
func (x SpanFlags) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (SpanFlags) Descriptor() protoreflect.EnumDescriptor {
return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0].Descriptor()
}
func (SpanFlags) Type() protoreflect.EnumType {
return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0]
}
func (x SpanFlags) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use SpanFlags.Descriptor instead.
func (SpanFlags) EnumDescriptor() ([]byte, []int) {
return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{0}
}
// SpanKind is the type of span. Can be used to specify additional relationships between spans // SpanKind is the type of span. Can be used to specify additional relationships between spans
// in addition to a parent/child relationship. // in addition to a parent/child relationship.
type Span_SpanKind int32 type Span_SpanKind int32
@ -94,11 +157,11 @@ func (x Span_SpanKind) String() string {
} }
func (Span_SpanKind) Descriptor() protoreflect.EnumDescriptor { func (Span_SpanKind) Descriptor() protoreflect.EnumDescriptor {
return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0].Descriptor() return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1].Descriptor()
} }
func (Span_SpanKind) Type() protoreflect.EnumType { func (Span_SpanKind) Type() protoreflect.EnumType {
return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0] return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1]
} }
func (x Span_SpanKind) Number() protoreflect.EnumNumber { func (x Span_SpanKind) Number() protoreflect.EnumNumber {
@ -149,11 +212,11 @@ func (x Status_StatusCode) String() string {
} }
func (Status_StatusCode) Descriptor() protoreflect.EnumDescriptor { func (Status_StatusCode) Descriptor() protoreflect.EnumDescriptor {
return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1].Descriptor() return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[2].Descriptor()
} }
func (Status_StatusCode) Type() protoreflect.EnumType { func (Status_StatusCode) Type() protoreflect.EnumType {
return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1] return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[2]
} }
func (x Status_StatusCode) Number() protoreflect.EnumNumber { func (x Status_StatusCode) Number() protoreflect.EnumNumber {
@ -238,6 +301,9 @@ type ResourceSpans struct {
Resource *v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` Resource *v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
// A list of ScopeSpans that originate from a resource. // A list of ScopeSpans that originate from a resource.
ScopeSpans []*ScopeSpans `protobuf:"bytes,2,rep,name=scope_spans,json=scopeSpans,proto3" json:"scope_spans,omitempty"` ScopeSpans []*ScopeSpans `protobuf:"bytes,2,rep,name=scope_spans,json=scopeSpans,proto3" json:"scope_spans,omitempty"`
// The Schema URL, if known. This is the identifier of the Schema that the resource data
// is recorded in. To learn more about Schema URL see
// https://opentelemetry.io/docs/specs/otel/schemas/#schema-url
// This schema_url applies to the data in the "resource" field. It does not apply // This schema_url applies to the data in the "resource" field. It does not apply
// to the data in the "scope_spans" field which have their own schema_url field. // to the data in the "scope_spans" field which have their own schema_url field.
SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"`
@ -308,6 +374,9 @@ type ScopeSpans struct {
Scope *v11.InstrumentationScope `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` Scope *v11.InstrumentationScope `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"`
// A list of Spans that originate from an instrumentation scope. // A list of Spans that originate from an instrumentation scope.
Spans []*Span `protobuf:"bytes,2,rep,name=spans,proto3" json:"spans,omitempty"` Spans []*Span `protobuf:"bytes,2,rep,name=spans,proto3" json:"spans,omitempty"`
// The Schema URL, if known. This is the identifier of the Schema that the span data
// is recorded in. To learn more about Schema URL see
// https://opentelemetry.io/docs/specs/otel/schemas/#schema-url
// This schema_url applies to all spans and span events in the "spans" field. // This schema_url applies to all spans and span events in the "spans" field.
SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"`
} }
@ -394,6 +463,21 @@ type Span struct {
// The `span_id` of this span's parent span. If this is a root span, then this // The `span_id` of this span's parent span. If this is a root span, then this
// field must be empty. The ID is an 8-byte array. // field must be empty. The ID is an 8-byte array.
ParentSpanId []byte `protobuf:"bytes,4,opt,name=parent_span_id,json=parentSpanId,proto3" json:"parent_span_id,omitempty"` ParentSpanId []byte `protobuf:"bytes,4,opt,name=parent_span_id,json=parentSpanId,proto3" json:"parent_span_id,omitempty"`
// Flags, a bit field. 8 least significant bits are the trace
// flags as defined in W3C Trace Context specification. Readers
// MUST not assume that 24 most significant bits will be zero.
// To read the 8-bit W3C trace flag, use `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`.
//
// When creating span messages, if the message is logically forwarded from another source
// with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD
// be copied as-is. If creating from a source that does not have an equivalent flags field
// (such as a runtime representation of an OpenTelemetry span), the high 24 bits MUST
// be set to zero.
//
// [Optional].
//
// See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions.
Flags uint32 `protobuf:"fixed32,16,opt,name=flags,proto3" json:"flags,omitempty"`
// A description of the span's operation. // A description of the span's operation.
// //
// For example, the name can be a qualified method name or a file name // For example, the name can be a qualified method name or a file name
@ -517,6 +601,13 @@ func (x *Span) GetParentSpanId() []byte {
return nil return nil
} }
func (x *Span) GetFlags() uint32 {
if x != nil {
return x.Flags
}
return 0
}
func (x *Span) GetName() string { func (x *Span) GetName() string {
if x != nil { if x != nil {
return x.Name return x.Name
@ -757,6 +848,15 @@ type Span_Link struct {
// dropped_attributes_count is the number of dropped attributes. If the value is 0, // dropped_attributes_count is the number of dropped attributes. If the value is 0,
// then no attributes were dropped. // then no attributes were dropped.
DroppedAttributesCount uint32 `protobuf:"varint,5,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` DroppedAttributesCount uint32 `protobuf:"varint,5,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"`
// Flags, a bit field. 8 least significant bits are the trace
// flags as defined in W3C Trace Context specification. Readers
// MUST not assume that 24 most significant bits will be zero.
// When creating new spans, the most-significant 24-bits MUST be
// zero. To read the 8-bit W3C trace flag (use flags &
// SPAN_FLAGS_TRACE_FLAGS_MASK). [Optional].
//
// See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions.
Flags uint32 `protobuf:"fixed32,6,opt,name=flags,proto3" json:"flags,omitempty"`
} }
func (x *Span_Link) Reset() { func (x *Span_Link) Reset() {
@ -826,6 +926,13 @@ func (x *Span_Link) GetDroppedAttributesCount() uint32 {
return 0 return 0
} }
func (x *Span_Link) GetFlags() uint32 {
if x != nil {
return x.Flags
}
return 0
}
var File_opentelemetry_proto_trace_v1_trace_proto protoreflect.FileDescriptor var File_opentelemetry_proto_trace_v1_trace_proto protoreflect.FileDescriptor
var file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = []byte{ var file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = []byte{
@ -869,7 +976,7 @@ func (x *Span_Link) GetDroppedAttributesCount() uint32 {
0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05,
0x73, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f,
0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d,
0x61, 0x55, 0x72, 0x6c, 0x22, 0x9c, 0x0a, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x19, 0x0a, 0x61, 0x55, 0x72, 0x6c, 0x22, 0xc8, 0x0a, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x19, 0x0a,
0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49,
@ -877,101 +984,108 @@ func (x *Span_Link) GetDroppedAttributesCount() uint32 {
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61,
0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x70, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x70, 0x61,
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x12,
0x6b, 0x69, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e,
0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x53, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79,
0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x2f, 0x0a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x14, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b,
0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x06, 0x52, 0x11, 0x73, 0x74, 0x61, 0x69, 0x6e, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d,
0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28,
0x0a, 0x12, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x06, 0x52, 0x11, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78,
0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0f, 0x65, 0x6e, 0x64, 0x54, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, 0x0a, 0x12, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x06,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x52, 0x0f, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e,
0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x6f, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65,
0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72,
0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43,
0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0b,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d,
0x61, 0x6e, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65,
0x12, 0x30, 0x0a, 0x14, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06,
0x74, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65,
0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x75, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c,
0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x45, 0x76, 0x65,
0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65,
0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b,
0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e,
0x6b, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11,
0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x43, 0x6f, 0x75, 0x6e,
0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a,
0xc4, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d,
0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28,
0x06, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65,
0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18,
0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16,
0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xde, 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12,
0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70,
0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61,
0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61,
0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53,
0x74, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74,
0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a,
0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x53, 0x70, 0x61, 0x6e,
0x4b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e,
0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54,
0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f,
0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a,
0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e,
0x54, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44,
0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x53,
0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45,
0x52, 0x10, 0x05, 0x22, 0xbd, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18,
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c,
0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61,
0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x4e, 0x0a,
0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53,
0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54,
0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44,
0x45, 0x5f, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53,
0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x4a, 0x04, 0x08,
0x01, 0x10, 0x02, 0x42, 0x77, 0x0a, 0x1f, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65,
0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72,
0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x6b,
0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x72, 0x6f, 0x70, 0x70,
0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e,
0x6f, 0x74, 0x6c, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x1c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4c, 0x69, 0x6e,
0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6b, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65,
0x6f, 0x74, 0x6f, 0x33, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72,
0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0xc4, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12,
0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e,
0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69,
0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e,
0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65,
0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xf4, 0x01, 0x0a,
0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64,
0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61,
0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b,
0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a,
0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c,
0x61, 0x67, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64,
0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e,
0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53,
0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41,
0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44,
0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41,
0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12,
0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x50, 0x52, 0x4f,
0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f,
0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, 0x52, 0x10, 0x05, 0x22,
0xbd, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74,
0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43,
0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x4e, 0x0a, 0x0a, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55,
0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, 0x12,
0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4f, 0x4b,
0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44,
0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x2a,
0x48, 0x0a, 0x09, 0x53, 0x70, 0x61, 0x6e, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x15,
0x53, 0x50, 0x41, 0x4e, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x44, 0x4f, 0x5f, 0x4e, 0x4f,
0x54, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1b, 0x53, 0x50, 0x41, 0x4e, 0x5f,
0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x46, 0x4c, 0x41, 0x47,
0x53, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0xff, 0x01, 0x42, 0x77, 0x0a, 0x1f, 0x69, 0x6f, 0x2e,
0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x72,
0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x6f, 0x2e, 0x6f,
0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65,
0x2f, 0x76, 0x31, 0xaa, 0x02, 0x1c, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65,
0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e,
0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -986,36 +1100,37 @@ func file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP() []byte {
return file_opentelemetry_proto_trace_v1_trace_proto_rawDescData return file_opentelemetry_proto_trace_v1_trace_proto_rawDescData
} }
var file_opentelemetry_proto_trace_v1_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_opentelemetry_proto_trace_v1_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_opentelemetry_proto_trace_v1_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_opentelemetry_proto_trace_v1_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_opentelemetry_proto_trace_v1_trace_proto_goTypes = []interface{}{ var file_opentelemetry_proto_trace_v1_trace_proto_goTypes = []interface{}{
(Span_SpanKind)(0), // 0: opentelemetry.proto.trace.v1.Span.SpanKind (SpanFlags)(0), // 0: opentelemetry.proto.trace.v1.SpanFlags
(Status_StatusCode)(0), // 1: opentelemetry.proto.trace.v1.Status.StatusCode (Span_SpanKind)(0), // 1: opentelemetry.proto.trace.v1.Span.SpanKind
(*TracesData)(nil), // 2: opentelemetry.proto.trace.v1.TracesData (Status_StatusCode)(0), // 2: opentelemetry.proto.trace.v1.Status.StatusCode
(*ResourceSpans)(nil), // 3: opentelemetry.proto.trace.v1.ResourceSpans (*TracesData)(nil), // 3: opentelemetry.proto.trace.v1.TracesData
(*ScopeSpans)(nil), // 4: opentelemetry.proto.trace.v1.ScopeSpans (*ResourceSpans)(nil), // 4: opentelemetry.proto.trace.v1.ResourceSpans
(*Span)(nil), // 5: opentelemetry.proto.trace.v1.Span (*ScopeSpans)(nil), // 5: opentelemetry.proto.trace.v1.ScopeSpans
(*Status)(nil), // 6: opentelemetry.proto.trace.v1.Status (*Span)(nil), // 6: opentelemetry.proto.trace.v1.Span
(*Span_Event)(nil), // 7: opentelemetry.proto.trace.v1.Span.Event (*Status)(nil), // 7: opentelemetry.proto.trace.v1.Status
(*Span_Link)(nil), // 8: opentelemetry.proto.trace.v1.Span.Link (*Span_Event)(nil), // 8: opentelemetry.proto.trace.v1.Span.Event
(*v1.Resource)(nil), // 9: opentelemetry.proto.resource.v1.Resource (*Span_Link)(nil), // 9: opentelemetry.proto.trace.v1.Span.Link
(*v11.InstrumentationScope)(nil), // 10: opentelemetry.proto.common.v1.InstrumentationScope (*v1.Resource)(nil), // 10: opentelemetry.proto.resource.v1.Resource
(*v11.KeyValue)(nil), // 11: opentelemetry.proto.common.v1.KeyValue (*v11.InstrumentationScope)(nil), // 11: opentelemetry.proto.common.v1.InstrumentationScope
(*v11.KeyValue)(nil), // 12: opentelemetry.proto.common.v1.KeyValue
} }
var file_opentelemetry_proto_trace_v1_trace_proto_depIdxs = []int32{ var file_opentelemetry_proto_trace_v1_trace_proto_depIdxs = []int32{
3, // 0: opentelemetry.proto.trace.v1.TracesData.resource_spans:type_name -> opentelemetry.proto.trace.v1.ResourceSpans 4, // 0: opentelemetry.proto.trace.v1.TracesData.resource_spans:type_name -> opentelemetry.proto.trace.v1.ResourceSpans
9, // 1: opentelemetry.proto.trace.v1.ResourceSpans.resource:type_name -> opentelemetry.proto.resource.v1.Resource 10, // 1: opentelemetry.proto.trace.v1.ResourceSpans.resource:type_name -> opentelemetry.proto.resource.v1.Resource
4, // 2: opentelemetry.proto.trace.v1.ResourceSpans.scope_spans:type_name -> opentelemetry.proto.trace.v1.ScopeSpans 5, // 2: opentelemetry.proto.trace.v1.ResourceSpans.scope_spans:type_name -> opentelemetry.proto.trace.v1.ScopeSpans
10, // 3: opentelemetry.proto.trace.v1.ScopeSpans.scope:type_name -> opentelemetry.proto.common.v1.InstrumentationScope 11, // 3: opentelemetry.proto.trace.v1.ScopeSpans.scope:type_name -> opentelemetry.proto.common.v1.InstrumentationScope
5, // 4: opentelemetry.proto.trace.v1.ScopeSpans.spans:type_name -> opentelemetry.proto.trace.v1.Span 6, // 4: opentelemetry.proto.trace.v1.ScopeSpans.spans:type_name -> opentelemetry.proto.trace.v1.Span
0, // 5: opentelemetry.proto.trace.v1.Span.kind:type_name -> opentelemetry.proto.trace.v1.Span.SpanKind 1, // 5: opentelemetry.proto.trace.v1.Span.kind:type_name -> opentelemetry.proto.trace.v1.Span.SpanKind
11, // 6: opentelemetry.proto.trace.v1.Span.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue 12, // 6: opentelemetry.proto.trace.v1.Span.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue
7, // 7: opentelemetry.proto.trace.v1.Span.events:type_name -> opentelemetry.proto.trace.v1.Span.Event 8, // 7: opentelemetry.proto.trace.v1.Span.events:type_name -> opentelemetry.proto.trace.v1.Span.Event
8, // 8: opentelemetry.proto.trace.v1.Span.links:type_name -> opentelemetry.proto.trace.v1.Span.Link 9, // 8: opentelemetry.proto.trace.v1.Span.links:type_name -> opentelemetry.proto.trace.v1.Span.Link
6, // 9: opentelemetry.proto.trace.v1.Span.status:type_name -> opentelemetry.proto.trace.v1.Status 7, // 9: opentelemetry.proto.trace.v1.Span.status:type_name -> opentelemetry.proto.trace.v1.Status
1, // 10: opentelemetry.proto.trace.v1.Status.code:type_name -> opentelemetry.proto.trace.v1.Status.StatusCode 2, // 10: opentelemetry.proto.trace.v1.Status.code:type_name -> opentelemetry.proto.trace.v1.Status.StatusCode
11, // 11: opentelemetry.proto.trace.v1.Span.Event.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue 12, // 11: opentelemetry.proto.trace.v1.Span.Event.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue
11, // 12: opentelemetry.proto.trace.v1.Span.Link.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue 12, // 12: opentelemetry.proto.trace.v1.Span.Link.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue
13, // [13:13] is the sub-list for method output_type 13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type 13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name 13, // [13:13] is the sub-list for extension type_name
@ -1119,7 +1234,7 @@ type x struct{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_opentelemetry_proto_trace_v1_trace_proto_rawDesc, RawDescriptor: file_opentelemetry_proto_trace_v1_trace_proto_rawDesc,
NumEnums: 2, NumEnums: 3,
NumMessages: 7, NumMessages: 7,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,

View file

@ -32,21 +32,13 @@
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
) )
type ccbMode int
const (
ccbModeActive = iota
ccbModeIdle
ccbModeClosed
ccbModeExitingIdle
)
// ccBalancerWrapper sits between the ClientConn and the Balancer. // ccBalancerWrapper sits between the ClientConn and the Balancer.
// //
// ccBalancerWrapper implements methods corresponding to the ones on the // ccBalancerWrapper implements methods corresponding to the ones on the
// balancer.Balancer interface. The ClientConn is free to call these methods // balancer.Balancer interface. The ClientConn is free to call these methods
// concurrently and the ccBalancerWrapper ensures that calls from the ClientConn // concurrently and the ccBalancerWrapper ensures that calls from the ClientConn
// to the Balancer happen synchronously and in order. // to the Balancer happen in order by performing them in the serializer, without
// any mutexes held.
// //
// ccBalancerWrapper also implements the balancer.ClientConn interface and is // ccBalancerWrapper also implements the balancer.ClientConn interface and is
// passed to the Balancer implementations. It invokes unexported methods on the // passed to the Balancer implementations. It invokes unexported methods on the
@ -57,87 +49,75 @@
type ccBalancerWrapper struct { type ccBalancerWrapper struct {
// The following fields are initialized when the wrapper is created and are // The following fields are initialized when the wrapper is created and are
// read-only afterwards, and therefore can be accessed without a mutex. // read-only afterwards, and therefore can be accessed without a mutex.
cc *ClientConn cc *ClientConn
opts balancer.BuildOptions opts balancer.BuildOptions
serializer *grpcsync.CallbackSerializer
serializerCancel context.CancelFunc
// Outgoing (gRPC --> balancer) calls are guaranteed to execute in a // The following fields are only accessed within the serializer or during
// mutually exclusive manner as they are scheduled in the serializer. Fields // initialization.
// accessed *only* in these serializer callbacks, can therefore be accessed
// without a mutex.
balancer *gracefulswitch.Balancer
curBalancerName string curBalancerName string
balancer *gracefulswitch.Balancer
// mu guards access to the below fields. Access to the serializer and its // The following field is protected by mu. Caller must take cc.mu before
// cancel function needs to be mutex protected because they are overwritten // taking mu.
// when the wrapper exits idle mode. mu sync.Mutex
mu sync.Mutex closed bool
serializer *grpcsync.CallbackSerializer // To serialize all outoing calls.
serializerCancel context.CancelFunc // To close the seralizer at close/enterIdle time.
mode ccbMode // Tracks the current mode of the wrapper.
} }
// newCCBalancerWrapper creates a new balancer wrapper. The underlying balancer // newCCBalancerWrapper creates a new balancer wrapper in idle state. The
// is not created until the switchTo() method is invoked. // underlying balancer is not created until the switchTo() method is invoked.
func newCCBalancerWrapper(cc *ClientConn, bopts balancer.BuildOptions) *ccBalancerWrapper { func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(cc.ctx)
ccb := &ccBalancerWrapper{ ccb := &ccBalancerWrapper{
cc: cc, cc: cc,
opts: bopts, opts: balancer.BuildOptions{
DialCreds: cc.dopts.copts.TransportCredentials,
CredsBundle: cc.dopts.copts.CredsBundle,
Dialer: cc.dopts.copts.Dialer,
Authority: cc.authority,
CustomUserAgent: cc.dopts.copts.UserAgent,
ChannelzParentID: cc.channelzID,
Target: cc.parsedTarget,
},
serializer: grpcsync.NewCallbackSerializer(ctx), serializer: grpcsync.NewCallbackSerializer(ctx),
serializerCancel: cancel, serializerCancel: cancel,
} }
ccb.balancer = gracefulswitch.NewBalancer(ccb, bopts) ccb.balancer = gracefulswitch.NewBalancer(ccb, ccb.opts)
return ccb return ccb
} }
// updateClientConnState is invoked by grpc to push a ClientConnState update to // updateClientConnState is invoked by grpc to push a ClientConnState update to
// the underlying balancer. // the underlying balancer. This is always executed from the serializer, so
// it is safe to call into the balancer here.
func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error { func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error {
ccb.mu.Lock() errCh := make(chan error)
errCh := make(chan error, 1) ok := ccb.serializer.Schedule(func(ctx context.Context) {
// Here and everywhere else where Schedule() is called, it is done with the defer close(errCh)
// lock held. But the lock guards only the scheduling part. The actual if ctx.Err() != nil || ccb.balancer == nil {
// callback is called asynchronously without the lock being held. return
ok := ccb.serializer.Schedule(func(_ context.Context) { }
errCh <- ccb.balancer.UpdateClientConnState(*ccs) err := ccb.balancer.UpdateClientConnState(*ccs)
if logger.V(2) && err != nil {
logger.Infof("error from balancer.UpdateClientConnState: %v", err)
}
errCh <- err
}) })
if !ok { if !ok {
// If we are unable to schedule a function with the serializer, it return nil
// indicates that it has been closed. A serializer is only closed when
// the wrapper is closed or is in idle.
ccb.mu.Unlock()
return fmt.Errorf("grpc: cannot send state update to a closed or idle balancer")
} }
ccb.mu.Unlock() return <-errCh
// We get here only if the above call to Schedule succeeds, in which case it
// is guaranteed that the scheduled function will run. Therefore it is safe
// to block on this channel.
err := <-errCh
if logger.V(2) && err != nil {
logger.Infof("error from balancer.UpdateClientConnState: %v", err)
}
return err
}
// updateSubConnState is invoked by grpc to push a subConn state update to the
// underlying balancer.
func (ccb *ccBalancerWrapper) updateSubConnState(sc balancer.SubConn, s connectivity.State, err error) {
ccb.mu.Lock()
ccb.serializer.Schedule(func(_ context.Context) {
// Even though it is optional for balancers, gracefulswitch ensures
// opts.StateListener is set, so this cannot ever be nil.
sc.(*acBalancerWrapper).stateListener(balancer.SubConnState{ConnectivityState: s, ConnectionError: err})
})
ccb.mu.Unlock()
} }
// resolverError is invoked by grpc to push a resolver error to the underlying
// balancer. The call to the balancer is executed from the serializer.
func (ccb *ccBalancerWrapper) resolverError(err error) { func (ccb *ccBalancerWrapper) resolverError(err error) {
ccb.mu.Lock() ccb.serializer.Schedule(func(ctx context.Context) {
ccb.serializer.Schedule(func(_ context.Context) { if ctx.Err() != nil || ccb.balancer == nil {
return
}
ccb.balancer.ResolverError(err) ccb.balancer.ResolverError(err)
}) })
ccb.mu.Unlock()
} }
// switchTo is invoked by grpc to instruct the balancer wrapper to switch to the // switchTo is invoked by grpc to instruct the balancer wrapper to switch to the
@ -151,8 +131,10 @@ func (ccb *ccBalancerWrapper) resolverError(err error) {
// the ccBalancerWrapper keeps track of the current LB policy name, and skips // the ccBalancerWrapper keeps track of the current LB policy name, and skips
// the graceful balancer switching process if the name does not change. // the graceful balancer switching process if the name does not change.
func (ccb *ccBalancerWrapper) switchTo(name string) { func (ccb *ccBalancerWrapper) switchTo(name string) {
ccb.mu.Lock() ccb.serializer.Schedule(func(ctx context.Context) {
ccb.serializer.Schedule(func(_ context.Context) { if ctx.Err() != nil || ccb.balancer == nil {
return
}
// TODO: Other languages use case-sensitive balancer registries. We should // TODO: Other languages use case-sensitive balancer registries. We should
// switch as well. See: https://github.com/grpc/grpc-go/issues/5288. // switch as well. See: https://github.com/grpc/grpc-go/issues/5288.
if strings.EqualFold(ccb.curBalancerName, name) { if strings.EqualFold(ccb.curBalancerName, name) {
@ -160,7 +142,6 @@ func (ccb *ccBalancerWrapper) switchTo(name string) {
} }
ccb.buildLoadBalancingPolicy(name) ccb.buildLoadBalancingPolicy(name)
}) })
ccb.mu.Unlock()
} }
// buildLoadBalancingPolicy performs the following: // buildLoadBalancingPolicy performs the following:
@ -187,115 +168,49 @@ func (ccb *ccBalancerWrapper) buildLoadBalancingPolicy(name string) {
ccb.curBalancerName = builder.Name() ccb.curBalancerName = builder.Name()
} }
// close initiates async shutdown of the wrapper. cc.mu must be held when
// calling this function. To determine the wrapper has finished shutting down,
// the channel should block on ccb.serializer.Done() without cc.mu held.
func (ccb *ccBalancerWrapper) close() { func (ccb *ccBalancerWrapper) close() {
channelz.Info(logger, ccb.cc.channelzID, "ccBalancerWrapper: closing")
ccb.closeBalancer(ccbModeClosed)
}
// enterIdleMode is invoked by grpc when the channel enters idle mode upon
// expiry of idle_timeout. This call blocks until the balancer is closed.
func (ccb *ccBalancerWrapper) enterIdleMode() {
channelz.Info(logger, ccb.cc.channelzID, "ccBalancerWrapper: entering idle mode")
ccb.closeBalancer(ccbModeIdle)
}
// closeBalancer is invoked when the channel is being closed or when it enters
// idle mode upon expiry of idle_timeout.
func (ccb *ccBalancerWrapper) closeBalancer(m ccbMode) {
ccb.mu.Lock() ccb.mu.Lock()
if ccb.mode == ccbModeClosed || ccb.mode == ccbModeIdle { ccb.closed = true
ccb.mu.Unlock()
return
}
ccb.mode = m
done := ccb.serializer.Done()
b := ccb.balancer
ok := ccb.serializer.Schedule(func(_ context.Context) {
// Close the serializer to ensure that no more calls from gRPC are sent
// to the balancer.
ccb.serializerCancel()
// Empty the current balancer name because we don't have a balancer
// anymore and also so that we act on the next call to switchTo by
// creating a new balancer specified by the new resolver.
ccb.curBalancerName = ""
})
if !ok {
ccb.mu.Unlock()
return
}
ccb.mu.Unlock() ccb.mu.Unlock()
channelz.Info(logger, ccb.cc.channelzID, "ccBalancerWrapper: closing")
// Give enqueued callbacks a chance to finish before closing the balancer. ccb.serializer.Schedule(func(context.Context) {
<-done if ccb.balancer == nil {
b.Close()
}
// exitIdleMode is invoked by grpc when the channel exits idle mode either
// because of an RPC or because of an invocation of the Connect() API. This
// recreates the balancer that was closed previously when entering idle mode.
//
// If the channel is not in idle mode, we know for a fact that we are here as a
// result of the user calling the Connect() method on the ClientConn. In this
// case, we can simply forward the call to the underlying balancer, instructing
// it to reconnect to the backends.
func (ccb *ccBalancerWrapper) exitIdleMode() {
ccb.mu.Lock()
if ccb.mode == ccbModeClosed {
// Request to exit idle is a no-op when wrapper is already closed.
ccb.mu.Unlock()
return
}
if ccb.mode == ccbModeIdle {
// Recreate the serializer which was closed when we entered idle.
ctx, cancel := context.WithCancel(context.Background())
ccb.serializer = grpcsync.NewCallbackSerializer(ctx)
ccb.serializerCancel = cancel
}
// The ClientConn guarantees that mutual exclusion between close() and
// exitIdleMode(), and since we just created a new serializer, we can be
// sure that the below function will be scheduled.
done := make(chan struct{})
ccb.serializer.Schedule(func(_ context.Context) {
defer close(done)
ccb.mu.Lock()
defer ccb.mu.Unlock()
if ccb.mode != ccbModeIdle {
ccb.balancer.ExitIdle()
return return
} }
ccb.balancer.Close()
// Gracefulswitch balancer does not support a switchTo operation after ccb.balancer = nil
// being closed. Hence we need to create a new one here.
ccb.balancer = gracefulswitch.NewBalancer(ccb, ccb.opts)
ccb.mode = ccbModeActive
channelz.Info(logger, ccb.cc.channelzID, "ccBalancerWrapper: exiting idle mode")
}) })
ccb.mu.Unlock() ccb.serializerCancel()
<-done
} }
func (ccb *ccBalancerWrapper) isIdleOrClosed() bool { // exitIdle invokes the balancer's exitIdle method in the serializer.
ccb.mu.Lock() func (ccb *ccBalancerWrapper) exitIdle() {
defer ccb.mu.Unlock() ccb.serializer.Schedule(func(ctx context.Context) {
return ccb.mode == ccbModeIdle || ccb.mode == ccbModeClosed if ctx.Err() != nil || ccb.balancer == nil {
return
}
ccb.balancer.ExitIdle()
})
} }
func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
if ccb.isIdleOrClosed() { ccb.cc.mu.Lock()
return nil, fmt.Errorf("grpc: cannot create SubConn when balancer is closed or idle") defer ccb.cc.mu.Unlock()
ccb.mu.Lock()
if ccb.closed {
ccb.mu.Unlock()
return nil, fmt.Errorf("balancer is being closed; no new SubConns allowed")
} }
ccb.mu.Unlock()
if len(addrs) == 0 { if len(addrs) == 0 {
return nil, fmt.Errorf("grpc: cannot create SubConn with empty address list") return nil, fmt.Errorf("grpc: cannot create SubConn with empty address list")
} }
ac, err := ccb.cc.newAddrConn(addrs, opts) ac, err := ccb.cc.newAddrConnLocked(addrs, opts)
if err != nil { if err != nil {
channelz.Warningf(logger, ccb.cc.channelzID, "acBalancerWrapper: NewSubConn: failed to newAddrConn: %v", err) channelz.Warningf(logger, ccb.cc.channelzID, "acBalancerWrapper: NewSubConn: failed to newAddrConn: %v", err)
return nil, err return nil, err
@ -316,10 +231,6 @@ func (ccb *ccBalancerWrapper) RemoveSubConn(sc balancer.SubConn) {
} }
func (ccb *ccBalancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) { func (ccb *ccBalancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) {
if ccb.isIdleOrClosed() {
return
}
acbw, ok := sc.(*acBalancerWrapper) acbw, ok := sc.(*acBalancerWrapper)
if !ok { if !ok {
return return
@ -328,25 +239,39 @@ func (ccb *ccBalancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resol
} }
func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) { func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) {
if ccb.isIdleOrClosed() { ccb.cc.mu.Lock()
defer ccb.cc.mu.Unlock()
ccb.mu.Lock()
if ccb.closed {
ccb.mu.Unlock()
return return
} }
ccb.mu.Unlock()
// Update picker before updating state. Even though the ordering here does // Update picker before updating state. Even though the ordering here does
// not matter, it can lead to multiple calls of Pick in the common start-up // not matter, it can lead to multiple calls of Pick in the common start-up
// case where we wait for ready and then perform an RPC. If the picker is // case where we wait for ready and then perform an RPC. If the picker is
// updated later, we could call the "connecting" picker when the state is // updated later, we could call the "connecting" picker when the state is
// updated, and then call the "ready" picker after the picker gets updated. // updated, and then call the "ready" picker after the picker gets updated.
ccb.cc.blockingpicker.updatePicker(s.Picker)
// Note that there is no need to check if the balancer wrapper was closed,
// as we know the graceful switch LB policy will not call cc if it has been
// closed.
ccb.cc.pickerWrapper.updatePicker(s.Picker)
ccb.cc.csMgr.updateState(s.ConnectivityState) ccb.cc.csMgr.updateState(s.ConnectivityState)
} }
func (ccb *ccBalancerWrapper) ResolveNow(o resolver.ResolveNowOptions) { func (ccb *ccBalancerWrapper) ResolveNow(o resolver.ResolveNowOptions) {
if ccb.isIdleOrClosed() { ccb.cc.mu.RLock()
defer ccb.cc.mu.RUnlock()
ccb.mu.Lock()
if ccb.closed {
ccb.mu.Unlock()
return return
} }
ccb.mu.Unlock()
ccb.cc.resolveNow(o) ccb.cc.resolveNowLocked(o)
} }
func (ccb *ccBalancerWrapper) Target() string { func (ccb *ccBalancerWrapper) Target() string {
@ -364,6 +289,20 @@ type acBalancerWrapper struct {
producers map[balancer.ProducerBuilder]*refCountedProducer producers map[balancer.ProducerBuilder]*refCountedProducer
} }
// updateState is invoked by grpc to push a subConn state update to the
// underlying balancer.
func (acbw *acBalancerWrapper) updateState(s connectivity.State, err error) {
acbw.ccb.serializer.Schedule(func(ctx context.Context) {
if ctx.Err() != nil || acbw.ccb.balancer == nil {
return
}
// Even though it is optional for balancers, gracefulswitch ensures
// opts.StateListener is set, so this cannot ever be nil.
// TODO: delete this comment when UpdateSubConnState is removed.
acbw.stateListener(balancer.SubConnState{ConnectivityState: s, ConnectionError: err})
})
}
func (acbw *acBalancerWrapper) String() string { func (acbw *acBalancerWrapper) String() string {
return fmt.Sprintf("SubConn(id:%d)", acbw.ac.channelzID.Int()) return fmt.Sprintf("SubConn(id:%d)", acbw.ac.channelzID.Int())
} }
@ -377,20 +316,7 @@ func (acbw *acBalancerWrapper) Connect() {
} }
func (acbw *acBalancerWrapper) Shutdown() { func (acbw *acBalancerWrapper) Shutdown() {
ccb := acbw.ccb acbw.ccb.cc.removeAddrConn(acbw.ac, errConnDrain)
if ccb.isIdleOrClosed() {
// It it safe to ignore this call when the balancer is closed or in idle
// because the ClientConn takes care of closing the connections.
//
// Not returning early from here when the balancer is closed or in idle
// leads to a deadlock though, because of the following sequence of
// calls when holding cc.mu:
// cc.exitIdleMode --> ccb.enterIdleMode --> gsw.Close -->
// ccb.RemoveAddrConn --> cc.removeAddrConn
return
}
ccb.cc.removeAddrConn(acbw.ac, errConnDrain)
} }
// NewStream begins a streaming RPC on the addrConn. If the addrConn is not // NewStream begins a streaming RPC on the addrConn. If the addrConn is not

View file

@ -430,7 +430,7 @@ type ClientHeader struct {
MethodName string `protobuf:"bytes,2,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` MethodName string `protobuf:"bytes,2,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"`
// A single process may be used to run multiple virtual // A single process may be used to run multiple virtual
// servers with different identities. // servers with different identities.
// The authority is the name of such a server identitiy. // The authority is the name of such a server identity.
// It is typically a portion of the URI in the form of // It is typically a portion of the URI in the form of
// <host> or <host>:<port> . // <host> or <host>:<port> .
Authority string `protobuf:"bytes,3,opt,name=authority,proto3" json:"authority,omitempty"` Authority string `protobuf:"bytes,3,opt,name=authority,proto3" json:"authority,omitempty"`

View file

@ -33,9 +33,7 @@
"google.golang.org/grpc/balancer/base" "google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/internal" "google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/idle" "google.golang.org/grpc/internal/idle"
@ -48,9 +46,9 @@
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
_ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin. _ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin.
_ "google.golang.org/grpc/internal/resolver/dns" // To register dns resolver.
_ "google.golang.org/grpc/internal/resolver/passthrough" // To register passthrough resolver. _ "google.golang.org/grpc/internal/resolver/passthrough" // To register passthrough resolver.
_ "google.golang.org/grpc/internal/resolver/unix" // To register unix resolver. _ "google.golang.org/grpc/internal/resolver/unix" // To register unix resolver.
_ "google.golang.org/grpc/resolver/dns" // To register dns resolver.
) )
const ( const (
@ -119,23 +117,8 @@ func (dcs *defaultConfigSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*ires
}, nil }, nil
} }
// DialContext creates a client connection to the given target. By default, it's // newClient returns a new client in idle mode.
// a non-blocking dial (the function won't wait for connections to be func newClient(target string, opts ...DialOption) (conn *ClientConn, err error) {
// established, and connecting happens in the background). To make it a blocking
// dial, use WithBlock() dial option.
//
// In the non-blocking case, the ctx does not act against the connection. It
// only controls the setup steps.
//
// In the blocking case, ctx can be used to cancel or expire the pending
// connection. Once this function returns, the cancellation and expiration of
// ctx will be noop. Users should call ClientConn.Close to terminate all the
// pending operations after this function returns.
//
// The target name syntax is defined in
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
// e.g. to use dns resolver, a "dns:///" prefix should be applied to the target.
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
cc := &ClientConn{ cc := &ClientConn{
target: target, target: target,
conns: make(map[*addrConn]struct{}), conns: make(map[*addrConn]struct{}),
@ -143,23 +126,11 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
czData: new(channelzData), czData: new(channelzData),
} }
// We start the channel off in idle mode, but kick it out of idle at the end
// of this method, instead of waiting for the first RPC. Other gRPC
// implementations do wait for the first RPC to kick the channel out of
// idle. But doing so would be a major behavior change for our users who are
// used to seeing the channel active after Dial.
//
// Taking this approach of kicking it out of idle at the end of this method
// allows us to share the code between channel creation and exiting idle
// mode. This will also make it easy for us to switch to starting the
// channel off in idle, if at all we ever get to do that.
cc.idlenessState = ccIdlenessStateIdle
cc.retryThrottler.Store((*retryThrottler)(nil)) cc.retryThrottler.Store((*retryThrottler)(nil))
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil}) cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil})
cc.ctx, cc.cancel = context.WithCancel(context.Background()) cc.ctx, cc.cancel = context.WithCancel(context.Background())
cc.exitIdleCond = sync.NewCond(&cc.mu)
// Apply dial options.
disableGlobalOpts := false disableGlobalOpts := false
for _, opt := range opts { for _, opt := range opts {
if _, ok := opt.(*disableGlobalDialOptions); ok { if _, ok := opt.(*disableGlobalDialOptions); ok {
@ -177,21 +148,9 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
for _, opt := range opts { for _, opt := range opts {
opt.apply(&cc.dopts) opt.apply(&cc.dopts)
} }
chainUnaryClientInterceptors(cc) chainUnaryClientInterceptors(cc)
chainStreamClientInterceptors(cc) chainStreamClientInterceptors(cc)
defer func() {
if err != nil {
cc.Close()
}
}()
// Register ClientConn with channelz.
cc.channelzRegistration(target)
cc.csMgr = newConnectivityStateManager(cc.ctx, cc.channelzID)
if err := cc.validateTransportCredentials(); err != nil { if err := cc.validateTransportCredentials(); err != nil {
return nil, err return nil, err
} }
@ -205,10 +164,80 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
} }
cc.mkp = cc.dopts.copts.KeepaliveParams cc.mkp = cc.dopts.copts.KeepaliveParams
if cc.dopts.copts.UserAgent != "" { // Register ClientConn with channelz.
cc.dopts.copts.UserAgent += " " + grpcUA cc.channelzRegistration(target)
} else {
cc.dopts.copts.UserAgent = grpcUA // TODO: Ideally it should be impossible to error from this function after
// channelz registration. This will require removing some channelz logs
// from the following functions that can error. Errors can be returned to
// the user, and successful logs can be emitted here, after the checks have
// passed and channelz is subsequently registered.
// Determine the resolver to use.
if err := cc.parseTargetAndFindResolver(); err != nil {
channelz.RemoveEntry(cc.channelzID)
return nil, err
}
if err = cc.determineAuthority(); err != nil {
channelz.RemoveEntry(cc.channelzID)
return nil, err
}
cc.csMgr = newConnectivityStateManager(cc.ctx, cc.channelzID)
cc.pickerWrapper = newPickerWrapper(cc.dopts.copts.StatsHandlers)
cc.initIdleStateLocked() // Safe to call without the lock, since nothing else has a reference to cc.
cc.idlenessMgr = idle.NewManager((*idler)(cc), cc.dopts.idleTimeout)
return cc, nil
}
// DialContext creates a client connection to the given target. By default, it's
// a non-blocking dial (the function won't wait for connections to be
// established, and connecting happens in the background). To make it a blocking
// dial, use WithBlock() dial option.
//
// In the non-blocking case, the ctx does not act against the connection. It
// only controls the setup steps.
//
// In the blocking case, ctx can be used to cancel or expire the pending
// connection. Once this function returns, the cancellation and expiration of
// ctx will be noop. Users should call ClientConn.Close to terminate all the
// pending operations after this function returns.
//
// The target name syntax is defined in
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
// e.g. to use dns resolver, a "dns:///" prefix should be applied to the target.
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
cc, err := newClient(target, opts...)
if err != nil {
return nil, err
}
// We start the channel off in idle mode, but kick it out of idle now,
// instead of waiting for the first RPC. Other gRPC implementations do wait
// for the first RPC to kick the channel out of idle. But doing so would be
// a major behavior change for our users who are used to seeing the channel
// active after Dial.
//
// Taking this approach of kicking it out of idle at the end of this method
// allows us to share the code between channel creation and exiting idle
// mode. This will also make it easy for us to switch to starting the
// channel off in idle, i.e. by making newClient exported.
defer func() {
if err != nil {
cc.Close()
}
}()
// This creates the name resolver, load balancer, etc.
if err := cc.idlenessMgr.ExitIdleMode(); err != nil {
return nil, err
}
// Return now for non-blocking dials.
if !cc.dopts.block {
return cc, nil
} }
if cc.dopts.timeout > 0 { if cc.dopts.timeout > 0 {
@ -231,49 +260,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
} }
}() }()
if cc.dopts.bs == nil {
cc.dopts.bs = backoff.DefaultExponential
}
// Determine the resolver to use.
if err := cc.parseTargetAndFindResolver(); err != nil {
return nil, err
}
if err = cc.determineAuthority(); err != nil {
return nil, err
}
if cc.dopts.scChan != nil {
// Blocking wait for the initial service config.
select {
case sc, ok := <-cc.dopts.scChan:
if ok {
cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
if cc.dopts.scChan != nil {
go cc.scWatcher()
}
// This creates the name resolver, load balancer, blocking picker etc.
if err := cc.exitIdleMode(); err != nil {
return nil, err
}
// Configure idleness support with configured idle timeout or default idle
// timeout duration. Idleness can be explicitly disabled by the user, by
// setting the dial option to 0.
cc.idlenessMgr = idle.NewManager(idle.ManagerOptions{Enforcer: (*idler)(cc), Timeout: cc.dopts.idleTimeout, Logger: logger})
// Return early for non-blocking dials.
if !cc.dopts.block {
return cc, nil
}
// A blocking dial blocks until the clientConn is ready. // A blocking dial blocks until the clientConn is ready.
for { for {
s := cc.GetState() s := cc.GetState()
@ -320,8 +306,8 @@ func (cc *ClientConn) addTraceEvent(msg string) {
type idler ClientConn type idler ClientConn
func (i *idler) EnterIdleMode() error { func (i *idler) EnterIdleMode() {
return (*ClientConn)(i).enterIdleMode() (*ClientConn)(i).enterIdleMode()
} }
func (i *idler) ExitIdleMode() error { func (i *idler) ExitIdleMode() error {
@ -329,117 +315,71 @@ func (i *idler) ExitIdleMode() error {
} }
// exitIdleMode moves the channel out of idle mode by recreating the name // exitIdleMode moves the channel out of idle mode by recreating the name
// resolver and load balancer. // resolver and load balancer. This should never be called directly; use
func (cc *ClientConn) exitIdleMode() error { // cc.idlenessMgr.ExitIdleMode instead.
func (cc *ClientConn) exitIdleMode() (err error) {
cc.mu.Lock() cc.mu.Lock()
if cc.conns == nil { if cc.conns == nil {
cc.mu.Unlock() cc.mu.Unlock()
return errConnClosing return errConnClosing
} }
if cc.idlenessState != ccIdlenessStateIdle {
channelz.Infof(logger, cc.channelzID, "ClientConn asked to exit idle mode, current mode is %v", cc.idlenessState)
cc.mu.Unlock()
return nil
}
defer func() {
// When Close() and exitIdleMode() race against each other, one of the
// following two can happen:
// - Close() wins the race and runs first. exitIdleMode() runs after, and
// sees that the ClientConn is already closed and hence returns early.
// - exitIdleMode() wins the race and runs first and recreates the balancer
// and releases the lock before recreating the resolver. If Close() runs
// in this window, it will wait for exitIdleMode to complete.
//
// We achieve this synchronization using the below condition variable.
cc.mu.Lock()
cc.idlenessState = ccIdlenessStateActive
cc.exitIdleCond.Signal()
cc.mu.Unlock()
}()
cc.idlenessState = ccIdlenessStateExitingIdle
exitedIdle := false
if cc.blockingpicker == nil {
cc.blockingpicker = newPickerWrapper(cc.dopts.copts.StatsHandlers)
} else {
cc.blockingpicker.exitIdleMode()
exitedIdle = true
}
var credsClone credentials.TransportCredentials
if creds := cc.dopts.copts.TransportCredentials; creds != nil {
credsClone = creds.Clone()
}
if cc.balancerWrapper == nil {
cc.balancerWrapper = newCCBalancerWrapper(cc, balancer.BuildOptions{
DialCreds: credsClone,
CredsBundle: cc.dopts.copts.CredsBundle,
Dialer: cc.dopts.copts.Dialer,
Authority: cc.authority,
CustomUserAgent: cc.dopts.copts.UserAgent,
ChannelzParentID: cc.channelzID,
Target: cc.parsedTarget,
})
} else {
cc.balancerWrapper.exitIdleMode()
}
cc.firstResolveEvent = grpcsync.NewEvent()
cc.mu.Unlock() cc.mu.Unlock()
// This needs to be called without cc.mu because this builds a new resolver // This needs to be called without cc.mu because this builds a new resolver
// which might update state or report error inline which needs to be handled // which might update state or report error inline, which would then need to
// by cc.updateResolverState() which also grabs cc.mu. // acquire cc.mu.
if err := cc.initResolverWrapper(credsClone); err != nil { if err := cc.resolverWrapper.start(); err != nil {
return err return err
} }
if exitedIdle { cc.addTraceEvent("exiting idle mode")
cc.addTraceEvent("exiting idle mode")
}
return nil return nil
} }
// enterIdleMode puts the channel in idle mode, and as part of it shuts down the // initIdleStateLocked initializes common state to how it should be while idle.
// name resolver, load balancer and any subchannels. func (cc *ClientConn) initIdleStateLocked() {
func (cc *ClientConn) enterIdleMode() error { cc.resolverWrapper = newCCResolverWrapper(cc)
cc.mu.Lock() cc.balancerWrapper = newCCBalancerWrapper(cc)
defer cc.mu.Unlock() cc.firstResolveEvent = grpcsync.NewEvent()
if cc.conns == nil {
return ErrClientConnClosing
}
if cc.idlenessState != ccIdlenessStateActive {
channelz.Warningf(logger, cc.channelzID, "ClientConn asked to enter idle mode, current mode is %v", cc.idlenessState)
return nil
}
// cc.conns == nil is a proxy for the ClientConn being closed. So, instead // cc.conns == nil is a proxy for the ClientConn being closed. So, instead
// of setting it to nil here, we recreate the map. This also means that we // of setting it to nil here, we recreate the map. This also means that we
// don't have to do this when exiting idle mode. // don't have to do this when exiting idle mode.
conns := cc.conns
cc.conns = make(map[*addrConn]struct{}) cc.conns = make(map[*addrConn]struct{})
}
// TODO: Currently, we close the resolver wrapper upon entering idle mode // enterIdleMode puts the channel in idle mode, and as part of it shuts down the
// and create a new one upon exiting idle mode. This means that the // name resolver, load balancer, and any subchannels. This should never be
// `cc.resolverWrapper` field would be overwritten everytime we exit idle // called directly; use cc.idlenessMgr.EnterIdleMode instead.
// mode. While this means that we need to hold `cc.mu` when accessing func (cc *ClientConn) enterIdleMode() {
// `cc.resolverWrapper`, it makes the code simpler in the wrapper. We should cc.mu.Lock()
// try to do the same for the balancer and picker wrappers too.
cc.resolverWrapper.close() if cc.conns == nil {
cc.blockingpicker.enterIdleMode() cc.mu.Unlock()
cc.balancerWrapper.enterIdleMode() return
}
conns := cc.conns
rWrapper := cc.resolverWrapper
rWrapper.close()
cc.pickerWrapper.reset()
bWrapper := cc.balancerWrapper
bWrapper.close()
cc.csMgr.updateState(connectivity.Idle) cc.csMgr.updateState(connectivity.Idle)
cc.idlenessState = ccIdlenessStateIdle
cc.addTraceEvent("entering idle mode") cc.addTraceEvent("entering idle mode")
go func() { cc.initIdleStateLocked()
for ac := range conns {
ac.tearDown(errConnIdling)
}
}()
return nil cc.mu.Unlock()
// Block until the name resolver and LB policy are closed.
<-rWrapper.serializer.Done()
<-bWrapper.serializer.Done()
// Close all subchannels after the LB policy is closed.
for ac := range conns {
ac.tearDown(errConnIdling)
}
} }
// validateTransportCredentials performs a series of checks on the configured // validateTransportCredentials performs a series of checks on the configured
@ -649,66 +589,35 @@ type ClientConn struct {
dopts dialOptions // Default and user specified dial options. dopts dialOptions // Default and user specified dial options.
channelzID *channelz.Identifier // Channelz identifier for the channel. channelzID *channelz.Identifier // Channelz identifier for the channel.
resolverBuilder resolver.Builder // See parseTargetAndFindResolver(). resolverBuilder resolver.Builder // See parseTargetAndFindResolver().
balancerWrapper *ccBalancerWrapper // Uses gracefulswitch.balancer underneath. idlenessMgr *idle.Manager
idlenessMgr idle.Manager
// The following provide their own synchronization, and therefore don't // The following provide their own synchronization, and therefore don't
// require cc.mu to be held to access them. // require cc.mu to be held to access them.
csMgr *connectivityStateManager csMgr *connectivityStateManager
blockingpicker *pickerWrapper pickerWrapper *pickerWrapper
safeConfigSelector iresolver.SafeConfigSelector safeConfigSelector iresolver.SafeConfigSelector
czData *channelzData czData *channelzData
retryThrottler atomic.Value // Updated from service config. retryThrottler atomic.Value // Updated from service config.
// firstResolveEvent is used to track whether the name resolver sent us at
// least one update. RPCs block on this event.
firstResolveEvent *grpcsync.Event
// mu protects the following fields. // mu protects the following fields.
// TODO: split mu so the same mutex isn't used for everything. // TODO: split mu so the same mutex isn't used for everything.
mu sync.RWMutex mu sync.RWMutex
resolverWrapper *ccResolverWrapper // Initialized in Dial; cleared in Close. resolverWrapper *ccResolverWrapper // Always recreated whenever entering idle to simplify Close.
balancerWrapper *ccBalancerWrapper // Always recreated whenever entering idle to simplify Close.
sc *ServiceConfig // Latest service config received from the resolver. sc *ServiceConfig // Latest service config received from the resolver.
conns map[*addrConn]struct{} // Set to nil on close. conns map[*addrConn]struct{} // Set to nil on close.
mkp keepalive.ClientParameters // May be updated upon receipt of a GoAway. mkp keepalive.ClientParameters // May be updated upon receipt of a GoAway.
idlenessState ccIdlenessState // Tracks idleness state of the channel. // firstResolveEvent is used to track whether the name resolver sent us at
exitIdleCond *sync.Cond // Signalled when channel exits idle. // least one update. RPCs block on this event. May be accessed without mu
// if we know we cannot be asked to enter idle mode while accessing it (e.g.
// when the idle manager has already been closed, or if we are already
// entering idle mode).
firstResolveEvent *grpcsync.Event
lceMu sync.Mutex // protects lastConnectionError lceMu sync.Mutex // protects lastConnectionError
lastConnectionError error lastConnectionError error
} }
// ccIdlenessState tracks the idleness state of the channel.
//
// Channels start off in `active` and move to `idle` after a period of
// inactivity. When moving back to `active` upon an incoming RPC, they
// transition through `exiting_idle`. This state is useful for synchronization
// with Close().
//
// This state tracking is mostly for self-protection. The idlenessManager is
// expected to keep track of the state as well, and is expected not to call into
// the ClientConn unnecessarily.
type ccIdlenessState int8
const (
ccIdlenessStateActive ccIdlenessState = iota
ccIdlenessStateIdle
ccIdlenessStateExitingIdle
)
func (s ccIdlenessState) String() string {
switch s {
case ccIdlenessStateActive:
return "active"
case ccIdlenessStateIdle:
return "idle"
case ccIdlenessStateExitingIdle:
return "exitingIdle"
default:
return "unknown"
}
}
// WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or // WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or
// ctx expires. A true value is returned in former case and false in latter. // ctx expires. A true value is returned in former case and false in latter.
// //
@ -748,29 +657,15 @@ func (cc *ClientConn) GetState() connectivity.State {
// Notice: This API is EXPERIMENTAL and may be changed or removed in a later // Notice: This API is EXPERIMENTAL and may be changed or removed in a later
// release. // release.
func (cc *ClientConn) Connect() { func (cc *ClientConn) Connect() {
cc.exitIdleMode() if err := cc.idlenessMgr.ExitIdleMode(); err != nil {
cc.addTraceEvent(err.Error())
return
}
// If the ClientConn was not in idle mode, we need to call ExitIdle on the // If the ClientConn was not in idle mode, we need to call ExitIdle on the
// LB policy so that connections can be created. // LB policy so that connections can be created.
cc.balancerWrapper.exitIdleMode() cc.mu.Lock()
} cc.balancerWrapper.exitIdle()
cc.mu.Unlock()
func (cc *ClientConn) scWatcher() {
for {
select {
case sc, ok := <-cc.dopts.scChan:
if !ok {
return
}
cc.mu.Lock()
// TODO: load balance policy runtime change is ignored.
// We may revisit this decision in the future.
cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
cc.mu.Unlock()
case <-cc.ctx.Done():
return
}
}
} }
// waitForResolvedAddrs blocks until the resolver has provided addresses or the // waitForResolvedAddrs blocks until the resolver has provided addresses or the
@ -804,11 +699,11 @@ func init() {
internal.SubscribeToConnectivityStateChanges = func(cc *ClientConn, s grpcsync.Subscriber) func() { internal.SubscribeToConnectivityStateChanges = func(cc *ClientConn, s grpcsync.Subscriber) func() {
return cc.csMgr.pubSub.Subscribe(s) return cc.csMgr.pubSub.Subscribe(s)
} }
internal.EnterIdleModeForTesting = func(cc *ClientConn) error { internal.EnterIdleModeForTesting = func(cc *ClientConn) {
return cc.enterIdleMode() cc.idlenessMgr.EnterIdleModeForTesting()
} }
internal.ExitIdleModeForTesting = func(cc *ClientConn) error { internal.ExitIdleModeForTesting = func(cc *ClientConn) error {
return cc.exitIdleMode() return cc.idlenessMgr.ExitIdleMode()
} }
} }
@ -824,9 +719,8 @@ func (cc *ClientConn) maybeApplyDefaultServiceConfig(addrs []resolver.Address) {
} }
} }
func (cc *ClientConn) updateResolverState(s resolver.State, err error) error { func (cc *ClientConn) updateResolverStateAndUnlock(s resolver.State, err error) error {
defer cc.firstResolveEvent.Fire() defer cc.firstResolveEvent.Fire()
cc.mu.Lock()
// Check if the ClientConn is already closed. Some fields (e.g. // Check if the ClientConn is already closed. Some fields (e.g.
// balancerWrapper) are set to nil when closing the ClientConn, and could // balancerWrapper) are set to nil when closing the ClientConn, and could
// cause nil pointer panic if we don't have this check. // cause nil pointer panic if we don't have this check.
@ -872,7 +766,7 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
if cc.sc == nil { if cc.sc == nil {
// Apply the failing LB only if we haven't received valid service config // Apply the failing LB only if we haven't received valid service config
// from the name resolver in the past. // from the name resolver in the past.
cc.applyFailingLB(s.ServiceConfig) cc.applyFailingLBLocked(s.ServiceConfig)
cc.mu.Unlock() cc.mu.Unlock()
return ret return ret
} }
@ -894,15 +788,13 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
return ret return ret
} }
// applyFailingLB is akin to configuring an LB policy on the channel which // applyFailingLBLocked is akin to configuring an LB policy on the channel which
// always fails RPCs. Here, an actual LB policy is not configured, but an always // always fails RPCs. Here, an actual LB policy is not configured, but an always
// erroring picker is configured, which returns errors with information about // erroring picker is configured, which returns errors with information about
// what was invalid in the received service config. A config selector with no // what was invalid in the received service config. A config selector with no
// service config is configured, and the connectivity state of the channel is // service config is configured, and the connectivity state of the channel is
// set to TransientFailure. // set to TransientFailure.
// func (cc *ClientConn) applyFailingLBLocked(sc *serviceconfig.ParseResult) {
// Caller must hold cc.mu.
func (cc *ClientConn) applyFailingLB(sc *serviceconfig.ParseResult) {
var err error var err error
if sc.Err != nil { if sc.Err != nil {
err = status.Errorf(codes.Unavailable, "error parsing service config: %v", sc.Err) err = status.Errorf(codes.Unavailable, "error parsing service config: %v", sc.Err)
@ -910,14 +802,10 @@ func (cc *ClientConn) applyFailingLB(sc *serviceconfig.ParseResult) {
err = status.Errorf(codes.Unavailable, "illegal service config type: %T", sc.Config) err = status.Errorf(codes.Unavailable, "illegal service config type: %T", sc.Config)
} }
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil}) cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil})
cc.blockingpicker.updatePicker(base.NewErrPicker(err)) cc.pickerWrapper.updatePicker(base.NewErrPicker(err))
cc.csMgr.updateState(connectivity.TransientFailure) cc.csMgr.updateState(connectivity.TransientFailure)
} }
func (cc *ClientConn) handleSubConnStateChange(sc balancer.SubConn, s connectivity.State, err error) {
cc.balancerWrapper.updateSubConnState(sc, s, err)
}
// Makes a copy of the input addresses slice and clears out the balancer // Makes a copy of the input addresses slice and clears out the balancer
// attributes field. Addresses are passed during subconn creation and address // attributes field. Addresses are passed during subconn creation and address
// update operations. In both cases, we will clear the balancer attributes by // update operations. In both cases, we will clear the balancer attributes by
@ -932,10 +820,14 @@ func copyAddressesWithoutBalancerAttributes(in []resolver.Address) []resolver.Ad
return out return out
} }
// newAddrConn creates an addrConn for addrs and adds it to cc.conns. // newAddrConnLocked creates an addrConn for addrs and adds it to cc.conns.
// //
// Caller needs to make sure len(addrs) > 0. // Caller needs to make sure len(addrs) > 0.
func (cc *ClientConn) newAddrConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (*addrConn, error) { func (cc *ClientConn) newAddrConnLocked(addrs []resolver.Address, opts balancer.NewSubConnOptions) (*addrConn, error) {
if cc.conns == nil {
return nil, ErrClientConnClosing
}
ac := &addrConn{ ac := &addrConn{
state: connectivity.Idle, state: connectivity.Idle,
cc: cc, cc: cc,
@ -947,12 +839,6 @@ func (cc *ClientConn) newAddrConn(addrs []resolver.Address, opts balancer.NewSub
stateChan: make(chan struct{}), stateChan: make(chan struct{}),
} }
ac.ctx, ac.cancel = context.WithCancel(cc.ctx) ac.ctx, ac.cancel = context.WithCancel(cc.ctx)
// Track ac in cc. This needs to be done before any getTransport(...) is called.
cc.mu.Lock()
defer cc.mu.Unlock()
if cc.conns == nil {
return nil, ErrClientConnClosing
}
var err error var err error
ac.channelzID, err = channelz.RegisterSubChannel(ac, cc.channelzID, "") ac.channelzID, err = channelz.RegisterSubChannel(ac, cc.channelzID, "")
@ -968,6 +854,7 @@ func (cc *ClientConn) newAddrConn(addrs []resolver.Address, opts balancer.NewSub
}, },
}) })
// Track ac in cc. This needs to be done before any getTransport(...) is called.
cc.conns[ac] = struct{}{} cc.conns[ac] = struct{}{}
return ac, nil return ac, nil
} }
@ -1174,7 +1061,7 @@ func (cc *ClientConn) healthCheckConfig() *healthCheckConfig {
} }
func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method string) (transport.ClientTransport, balancer.PickResult, error) { func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method string) (transport.ClientTransport, balancer.PickResult, error) {
return cc.blockingpicker.pick(ctx, failfast, balancer.PickInfo{ return cc.pickerWrapper.pick(ctx, failfast, balancer.PickInfo{
Ctx: ctx, Ctx: ctx,
FullMethodName: method, FullMethodName: method,
}) })
@ -1216,12 +1103,12 @@ func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, configSel
func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) { func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) {
cc.mu.RLock() cc.mu.RLock()
r := cc.resolverWrapper cc.resolverWrapper.resolveNow(o)
cc.mu.RUnlock() cc.mu.RUnlock()
if r == nil { }
return
} func (cc *ClientConn) resolveNowLocked(o resolver.ResolveNowOptions) {
go r.resolveNow(o) cc.resolverWrapper.resolveNow(o)
} }
// ResetConnectBackoff wakes up all subchannels in transient failure and causes // ResetConnectBackoff wakes up all subchannels in transient failure and causes
@ -1253,40 +1140,32 @@ func (cc *ClientConn) Close() error {
<-cc.csMgr.pubSub.Done() <-cc.csMgr.pubSub.Done()
}() }()
// Prevent calls to enter/exit idle immediately, and ensure we are not
// currently entering/exiting idle mode.
cc.idlenessMgr.Close()
cc.mu.Lock() cc.mu.Lock()
if cc.conns == nil { if cc.conns == nil {
cc.mu.Unlock() cc.mu.Unlock()
return ErrClientConnClosing return ErrClientConnClosing
} }
for cc.idlenessState == ccIdlenessStateExitingIdle {
cc.exitIdleCond.Wait()
}
conns := cc.conns conns := cc.conns
cc.conns = nil cc.conns = nil
cc.csMgr.updateState(connectivity.Shutdown) cc.csMgr.updateState(connectivity.Shutdown)
pWrapper := cc.blockingpicker // We can safely unlock and continue to access all fields now as
rWrapper := cc.resolverWrapper // cc.conns==nil, preventing any further operations on cc.
bWrapper := cc.balancerWrapper
idlenessMgr := cc.idlenessMgr
cc.mu.Unlock() cc.mu.Unlock()
cc.resolverWrapper.close()
// The order of closing matters here since the balancer wrapper assumes the // The order of closing matters here since the balancer wrapper assumes the
// picker is closed before it is closed. // picker is closed before it is closed.
if pWrapper != nil { cc.pickerWrapper.close()
pWrapper.close() cc.balancerWrapper.close()
}
if bWrapper != nil { <-cc.resolverWrapper.serializer.Done()
bWrapper.close() <-cc.balancerWrapper.serializer.Done()
}
if rWrapper != nil {
rWrapper.close()
}
if idlenessMgr != nil {
idlenessMgr.Close()
}
for ac := range conns { for ac := range conns {
ac.tearDown(ErrClientConnClosing) ac.tearDown(ErrClientConnClosing)
@ -1307,7 +1186,7 @@ type addrConn struct {
cc *ClientConn cc *ClientConn
dopts dialOptions dopts dialOptions
acbw balancer.SubConn acbw *acBalancerWrapper
scopts balancer.NewSubConnOptions scopts balancer.NewSubConnOptions
// transport is set when there's a viable transport (note: ac state may not be READY as LB channel // transport is set when there's a viable transport (note: ac state may not be READY as LB channel
@ -1345,7 +1224,7 @@ func (ac *addrConn) updateConnectivityState(s connectivity.State, lastErr error)
} else { } else {
channelz.Infof(logger, ac.channelzID, "Subchannel Connectivity change to %v, last error: %s", s, lastErr) channelz.Infof(logger, ac.channelzID, "Subchannel Connectivity change to %v, last error: %s", s, lastErr)
} }
ac.cc.handleSubConnStateChange(ac.acbw, s, lastErr) ac.acbw.updateState(s, lastErr)
} }
// adjustParams updates parameters used to create transports upon // adjustParams updates parameters used to create transports upon
@ -1849,7 +1728,7 @@ func (cc *ClientConn) parseTargetAndFindResolver() error {
if err != nil { if err != nil {
channelz.Infof(logger, cc.channelzID, "dial target %q parse failed: %v", cc.target, err) channelz.Infof(logger, cc.channelzID, "dial target %q parse failed: %v", cc.target, err)
} else { } else {
channelz.Infof(logger, cc.channelzID, "parsed dial target is: %+v", parsedTarget) channelz.Infof(logger, cc.channelzID, "parsed dial target is: %#v", parsedTarget)
rb = cc.getResolver(parsedTarget.URL.Scheme) rb = cc.getResolver(parsedTarget.URL.Scheme)
if rb != nil { if rb != nil {
cc.parsedTarget = parsedTarget cc.parsedTarget = parsedTarget
@ -1981,58 +1860,17 @@ func (cc *ClientConn) determineAuthority() error {
} }
endpoint := cc.parsedTarget.Endpoint() endpoint := cc.parsedTarget.Endpoint()
target := cc.target if authorityFromDialOption != "" {
switch {
case authorityFromDialOption != "":
cc.authority = authorityFromDialOption cc.authority = authorityFromDialOption
case authorityFromCreds != "": } else if authorityFromCreds != "" {
cc.authority = authorityFromCreds cc.authority = authorityFromCreds
case strings.HasPrefix(target, "unix:") || strings.HasPrefix(target, "unix-abstract:"): } else if auth, ok := cc.resolverBuilder.(resolver.AuthorityOverrider); ok {
// TODO: remove when the unix resolver implements optional interface to cc.authority = auth.OverrideAuthority(cc.parsedTarget)
// return channel authority. } else if strings.HasPrefix(endpoint, ":") {
cc.authority = "localhost"
case strings.HasPrefix(endpoint, ":"):
cc.authority = "localhost" + endpoint cc.authority = "localhost" + endpoint
default: } else {
// TODO: Define an optional interface on the resolver builder to return
// the channel authority given the user's dial target. For resolvers
// which don't implement this interface, we will use the endpoint from
// "scheme://authority/endpoint" as the default authority.
// Escape the endpoint to handle use cases where the endpoint
// might not be a valid authority by default.
// For example an endpoint which has multiple paths like
// 'a/b/c', which is not a valid authority by default.
cc.authority = encodeAuthority(endpoint) cc.authority = encodeAuthority(endpoint)
} }
channelz.Infof(logger, cc.channelzID, "Channel authority set to %q", cc.authority) channelz.Infof(logger, cc.channelzID, "Channel authority set to %q", cc.authority)
return nil return nil
} }
// initResolverWrapper creates a ccResolverWrapper, which builds the name
// resolver. This method grabs the lock to assign the newly built resolver
// wrapper to the cc.resolverWrapper field.
func (cc *ClientConn) initResolverWrapper(creds credentials.TransportCredentials) error {
rw, err := newCCResolverWrapper(cc, ccResolverWrapperOpts{
target: cc.parsedTarget,
builder: cc.resolverBuilder,
bOpts: resolver.BuildOptions{
DisableServiceConfig: cc.dopts.disableServiceConfig,
DialCreds: creds,
CredsBundle: cc.dopts.copts.CredsBundle,
Dialer: cc.dopts.copts.Dialer,
},
channelzID: cc.channelzID,
})
if err != nil {
return fmt.Errorf("failed to build resolver: %v", err)
}
// Resolver implementations may report state update or error inline when
// built (or right after), and this is handled in cc.updateResolverState.
// Also, an error from the resolver might lead to a re-resolution request
// from the balancer, which is handled in resolveNow() where
// `cc.resolverWrapper` is accessed. Hence, we need to hold the lock here.
cc.mu.Lock()
cc.resolverWrapper = rw
cc.mu.Unlock()
return nil
}

View file

@ -25,7 +25,13 @@
"strconv" "strconv"
) )
// A Code is an unsigned 32-bit error code as defined in the gRPC spec. // A Code is a status code defined according to the [gRPC documentation].
//
// Only the codes defined as consts in this package are valid codes. Do not use
// other code values. Behavior of other codes is implementation-specific and
// interoperability between implementations is not guaranteed.
//
// [gRPC documentation]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
type Code uint32 type Code uint32
const ( const (

View file

@ -44,10 +44,25 @@ func (t TLSInfo) AuthType() string {
return "tls" return "tls"
} }
// cipherSuiteLookup returns the string version of a TLS cipher suite ID.
func cipherSuiteLookup(cipherSuiteID uint16) string {
for _, s := range tls.CipherSuites() {
if s.ID == cipherSuiteID {
return s.Name
}
}
for _, s := range tls.InsecureCipherSuites() {
if s.ID == cipherSuiteID {
return s.Name
}
}
return fmt.Sprintf("unknown ID: %v", cipherSuiteID)
}
// GetSecurityValue returns security info requested by channelz. // GetSecurityValue returns security info requested by channelz.
func (t TLSInfo) GetSecurityValue() ChannelzSecurityValue { func (t TLSInfo) GetSecurityValue() ChannelzSecurityValue {
v := &TLSChannelzSecurityValue{ v := &TLSChannelzSecurityValue{
StandardName: cipherSuiteLookup[t.State.CipherSuite], StandardName: cipherSuiteLookup(t.State.CipherSuite),
} }
// Currently there's no way to get LocalCertificate info from tls package. // Currently there's no way to get LocalCertificate info from tls package.
if len(t.State.PeerCertificates) > 0 { if len(t.State.PeerCertificates) > 0 {
@ -138,10 +153,39 @@ func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
return nil return nil
} }
// The following cipher suites are forbidden for use with HTTP/2 by
// https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
var tls12ForbiddenCipherSuites = map[uint16]struct{}{
tls.TLS_RSA_WITH_AES_128_CBC_SHA: {},
tls.TLS_RSA_WITH_AES_256_CBC_SHA: {},
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: {},
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: {},
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {},
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {},
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {},
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {},
}
// NewTLS uses c to construct a TransportCredentials based on TLS. // NewTLS uses c to construct a TransportCredentials based on TLS.
func NewTLS(c *tls.Config) TransportCredentials { func NewTLS(c *tls.Config) TransportCredentials {
tc := &tlsCreds{credinternal.CloneTLSConfig(c)} tc := &tlsCreds{credinternal.CloneTLSConfig(c)}
tc.config.NextProtos = credinternal.AppendH2ToNextProtos(tc.config.NextProtos) tc.config.NextProtos = credinternal.AppendH2ToNextProtos(tc.config.NextProtos)
// If the user did not configure a MinVersion and did not configure a
// MaxVersion < 1.2, use MinVersion=1.2, which is required by
// https://datatracker.ietf.org/doc/html/rfc7540#section-9.2
if tc.config.MinVersion == 0 && (tc.config.MaxVersion == 0 || tc.config.MaxVersion >= tls.VersionTLS12) {
tc.config.MinVersion = tls.VersionTLS12
}
// If the user did not configure CipherSuites, use all "secure" cipher
// suites reported by the TLS package, but remove some explicitly forbidden
// by https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
if tc.config.CipherSuites == nil {
for _, cs := range tls.CipherSuites() {
if _, ok := tls12ForbiddenCipherSuites[cs.ID]; !ok {
tc.config.CipherSuites = append(tc.config.CipherSuites, cs.ID)
}
}
}
return tc return tc
} }
@ -205,32 +249,3 @@ type TLSChannelzSecurityValue struct {
LocalCertificate []byte LocalCertificate []byte
RemoteCertificate []byte RemoteCertificate []byte
} }
var cipherSuiteLookup = map[uint16]string{
tls.TLS_RSA_WITH_RC4_128_SHA: "TLS_RSA_WITH_RC4_128_SHA",
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
tls.TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA",
tls.TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA",
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: "TLS_RSA_WITH_AES_128_GCM_SHA256",
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384",
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
tls.TLS_FALLBACK_SCSV: "TLS_FALLBACK_SCSV",
tls.TLS_RSA_WITH_AES_128_CBC_SHA256: "TLS_RSA_WITH_AES_128_CBC_SHA256",
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
tls.TLS_AES_128_GCM_SHA256: "TLS_AES_128_GCM_SHA256",
tls.TLS_AES_256_GCM_SHA384: "TLS_AES_256_GCM_SHA384",
tls.TLS_CHACHA20_POLY1305_SHA256: "TLS_CHACHA20_POLY1305_SHA256",
}

View file

@ -46,6 +46,7 @@ func init() {
internal.WithBinaryLogger = withBinaryLogger internal.WithBinaryLogger = withBinaryLogger
internal.JoinDialOptions = newJoinDialOption internal.JoinDialOptions = newJoinDialOption
internal.DisableGlobalDialOptions = newDisableGlobalDialOptions internal.DisableGlobalDialOptions = newDisableGlobalDialOptions
internal.WithRecvBufferPool = withRecvBufferPool
} }
// dialOptions configure a Dial call. dialOptions are set by the DialOption // dialOptions configure a Dial call. dialOptions are set by the DialOption
@ -63,7 +64,6 @@ type dialOptions struct {
block bool block bool
returnLastError bool returnLastError bool
timeout time.Duration timeout time.Duration
scChan <-chan ServiceConfig
authority string authority string
binaryLogger binarylog.Logger binaryLogger binarylog.Logger
copts transport.ConnectOptions copts transport.ConnectOptions
@ -250,19 +250,6 @@ func WithDecompressor(dc Decompressor) DialOption {
}) })
} }
// WithServiceConfig returns a DialOption which has a channel to read the
// service configuration.
//
// Deprecated: service config should be received through name resolver or via
// WithDefaultServiceConfig, as specified at
// https://github.com/grpc/grpc/blob/master/doc/service_config.md. Will be
// removed in a future 1.x release.
func WithServiceConfig(c <-chan ServiceConfig) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.scChan = c
})
}
// WithConnectParams configures the ClientConn to use the provided ConnectParams // WithConnectParams configures the ClientConn to use the provided ConnectParams
// for creating and maintaining connections to servers. // for creating and maintaining connections to servers.
// //
@ -413,6 +400,17 @@ func WithTimeout(d time.Duration) DialOption {
// connections. If FailOnNonTempDialError() is set to true, and an error is // connections. If FailOnNonTempDialError() is set to true, and an error is
// returned by f, gRPC checks the error's Temporary() method to decide if it // returned by f, gRPC checks the error's Temporary() method to decide if it
// should try to reconnect to the network address. // should try to reconnect to the network address.
//
// Note: All supported releases of Go (as of December 2023) override the OS
// defaults for TCP keepalive time and interval to 15s. To enable TCP keepalive
// with OS defaults for keepalive time and interval, use a net.Dialer that sets
// the KeepAlive field to a negative value, and sets the SO_KEEPALIVE socket
// option to true from the Control field. For a concrete example of how to do
// this, see internal.NetDialerWithTCPKeepalive().
//
// For more information, please see [issue 23459] in the Go github repo.
//
// [issue 23459]: https://github.com/golang/go/issues/23459
func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption { func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.copts.Dialer = f o.copts.Dialer = f
@ -487,7 +485,7 @@ func FailOnNonTempDialError(f bool) DialOption {
// the RPCs. // the RPCs.
func WithUserAgent(s string) DialOption { func WithUserAgent(s string) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.copts.UserAgent = s o.copts.UserAgent = s + " " + grpcUA
}) })
} }
@ -637,14 +635,16 @@ func withHealthCheckFunc(f internal.HealthChecker) DialOption {
func defaultDialOptions() dialOptions { func defaultDialOptions() dialOptions {
return dialOptions{ return dialOptions{
healthCheckFunc: internal.HealthCheckFunc,
copts: transport.ConnectOptions{ copts: transport.ConnectOptions{
WriteBufferSize: defaultWriteBufSize,
ReadBufferSize: defaultReadBufSize, ReadBufferSize: defaultReadBufSize,
WriteBufferSize: defaultWriteBufSize,
UseProxy: true, UseProxy: true,
UserAgent: grpcUA,
}, },
recvBufferPool: nopBufferPool{}, bs: internalbackoff.DefaultExponential,
idleTimeout: 30 * time.Minute, healthCheckFunc: internal.HealthCheckFunc,
idleTimeout: 30 * time.Minute,
recvBufferPool: nopBufferPool{},
} }
} }
@ -705,11 +705,13 @@ func WithIdleTimeout(d time.Duration) DialOption {
// options are used: WithStatsHandler, EnableTracing, or binary logging. In such // options are used: WithStatsHandler, EnableTracing, or binary logging. In such
// cases, the shared buffer pool will be ignored. // cases, the shared buffer pool will be ignored.
// //
// # Experimental // Deprecated: use experimental.WithRecvBufferPool instead. Will be deleted in
// // v1.60.0 or later.
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithRecvBufferPool(bufferPool SharedBufferPool) DialOption { func WithRecvBufferPool(bufferPool SharedBufferPool) DialOption {
return withRecvBufferPool(bufferPool)
}
func withRecvBufferPool(bufferPool SharedBufferPool) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.recvBufferPool = bufferPool o.recvBufferPool = bufferPool
}) })

View file

@ -18,7 +18,10 @@
// Package buffer provides an implementation of an unbounded buffer. // Package buffer provides an implementation of an unbounded buffer.
package buffer package buffer
import "sync" import (
"errors"
"sync"
)
// Unbounded is an implementation of an unbounded buffer which does not use // Unbounded is an implementation of an unbounded buffer which does not use
// extra goroutines. This is typically used for passing updates from one entity // extra goroutines. This is typically used for passing updates from one entity
@ -36,6 +39,7 @@
type Unbounded struct { type Unbounded struct {
c chan any c chan any
closed bool closed bool
closing bool
mu sync.Mutex mu sync.Mutex
backlog []any backlog []any
} }
@ -45,32 +49,32 @@ func NewUnbounded() *Unbounded {
return &Unbounded{c: make(chan any, 1)} return &Unbounded{c: make(chan any, 1)}
} }
var errBufferClosed = errors.New("Put called on closed buffer.Unbounded")
// Put adds t to the unbounded buffer. // Put adds t to the unbounded buffer.
func (b *Unbounded) Put(t any) { func (b *Unbounded) Put(t any) error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.closed { if b.closing {
return return errBufferClosed
} }
if len(b.backlog) == 0 { if len(b.backlog) == 0 {
select { select {
case b.c <- t: case b.c <- t:
return return nil
default: default:
} }
} }
b.backlog = append(b.backlog, t) b.backlog = append(b.backlog, t)
return nil
} }
// Load sends the earliest buffered data, if any, onto the read channel // Load sends the earliest buffered data, if any, onto the read channel returned
// returned by Get(). Users are expected to call this every time they read a // by Get(). Users are expected to call this every time they successfully read a
// value from the read channel. // value from the read channel.
func (b *Unbounded) Load() { func (b *Unbounded) Load() {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.closed {
return
}
if len(b.backlog) > 0 { if len(b.backlog) > 0 {
select { select {
case b.c <- b.backlog[0]: case b.c <- b.backlog[0]:
@ -78,6 +82,8 @@ func (b *Unbounded) Load() {
b.backlog = b.backlog[1:] b.backlog = b.backlog[1:]
default: default:
} }
} else if b.closing && !b.closed {
close(b.c)
} }
} }
@ -88,18 +94,23 @@ func (b *Unbounded) Load() {
// send the next buffered value onto the channel if there is any. // send the next buffered value onto the channel if there is any.
// //
// If the unbounded buffer is closed, the read channel returned by this method // If the unbounded buffer is closed, the read channel returned by this method
// is closed. // is closed after all data is drained.
func (b *Unbounded) Get() <-chan any { func (b *Unbounded) Get() <-chan any {
return b.c return b.c
} }
// Close closes the unbounded buffer. // Close closes the unbounded buffer. No subsequent data may be Put(), and the
// channel returned from Get() will be closed after all the data is read and
// Load() is called for the final time.
func (b *Unbounded) Close() { func (b *Unbounded) Close() {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.closed { if b.closing {
return return
} }
b.closed = true b.closing = true
close(b.c) if len(b.backlog) == 0 {
b.closed = true
close(b.c)
}
} }

View file

@ -31,6 +31,7 @@
"time" "time"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
) )
const ( const (
@ -58,6 +59,12 @@ func TurnOn() {
} }
} }
func init() {
internal.ChannelzTurnOffForTesting = func() {
atomic.StoreInt32(&curState, 0)
}
}
// IsOn returns whether channelz data collection is on. // IsOn returns whether channelz data collection is on.
func IsOn() bool { func IsOn() bool {
return atomic.LoadInt32(&curState) == 1 return atomic.LoadInt32(&curState) == 1

View file

@ -36,9 +36,6 @@
// "GRPC_RING_HASH_CAP". This does not override the default bounds // "GRPC_RING_HASH_CAP". This does not override the default bounds
// checking which NACKs configs specifying ring sizes > 8*1024*1024 (~8M). // checking which NACKs configs specifying ring sizes > 8*1024*1024 (~8M).
RingHashCap = uint64FromEnv("GRPC_RING_HASH_CAP", 4096, 1, 8*1024*1024) RingHashCap = uint64FromEnv("GRPC_RING_HASH_CAP", 4096, 1, 8*1024*1024)
// PickFirstLBConfig is set if we should support configuration of the
// pick_first LB policy.
PickFirstLBConfig = boolFromEnv("GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG", true)
// LeastRequestLB is set if we should support the least_request_experimental // LeastRequestLB is set if we should support the least_request_experimental
// LB policy, which can be enabled by setting the environment variable // LB policy, which can be enabled by setting the environment variable
// "GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST" to "true". // "GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST" to "true".

View file

@ -50,46 +50,7 @@
// //
// When both bootstrap FileName and FileContent are set, FileName is used. // When both bootstrap FileName and FileContent are set, FileName is used.
XDSBootstrapFileContent = os.Getenv(XDSBootstrapFileContentEnv) XDSBootstrapFileContent = os.Getenv(XDSBootstrapFileContentEnv)
// XDSRingHash indicates whether ring hash support is enabled, which can be
// disabled by setting the environment variable
// "GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH" to "false".
XDSRingHash = boolFromEnv("GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH", true)
// XDSClientSideSecurity is used to control processing of security
// configuration on the client-side.
//
// Note that there is no env var protection for the server-side because we
// have a brand new API on the server-side and users explicitly need to use
// the new API to get security integration on the server.
XDSClientSideSecurity = boolFromEnv("GRPC_XDS_EXPERIMENTAL_SECURITY_SUPPORT", true)
// XDSAggregateAndDNS indicates whether processing of aggregated cluster and
// DNS cluster is enabled, which can be disabled by setting the environment
// variable "GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER"
// to "false".
XDSAggregateAndDNS = boolFromEnv("GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER", true)
// XDSRBAC indicates whether xDS configured RBAC HTTP Filter is enabled,
// which can be disabled by setting the environment variable
// "GRPC_XDS_EXPERIMENTAL_RBAC" to "false".
XDSRBAC = boolFromEnv("GRPC_XDS_EXPERIMENTAL_RBAC", true)
// XDSOutlierDetection indicates whether outlier detection support is
// enabled, which can be disabled by setting the environment variable
// "GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION" to "false".
XDSOutlierDetection = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION", true)
// XDSFederation indicates whether federation support is enabled, which can
// be enabled by setting the environment variable
// "GRPC_EXPERIMENTAL_XDS_FEDERATION" to "true".
XDSFederation = boolFromEnv("GRPC_EXPERIMENTAL_XDS_FEDERATION", true)
// XDSRLS indicates whether processing of Cluster Specifier plugins and
// support for the RLS CLuster Specifier is enabled, which can be disabled by
// setting the environment variable "GRPC_EXPERIMENTAL_XDS_RLS_LB" to
// "false".
XDSRLS = boolFromEnv("GRPC_EXPERIMENTAL_XDS_RLS_LB", true)
// C2PResolverTestOnlyTrafficDirectorURI is the TD URI for testing. // C2PResolverTestOnlyTrafficDirectorURI is the TD URI for testing.
C2PResolverTestOnlyTrafficDirectorURI = os.Getenv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI") C2PResolverTestOnlyTrafficDirectorURI = os.Getenv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI")
// XDSCustomLBPolicy indicates whether Custom LB Policies are enabled, which
// can be disabled by setting the environment variable
// "GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG" to "false".
XDSCustomLBPolicy = boolFromEnv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG", true)
) )

28
vendor/google.golang.org/grpc/internal/experimental.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
/*
* Copyright 2023 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package internal
var (
// WithRecvBufferPool is implemented by the grpc package and returns a dial
// option to configure a shared buffer pool for a grpc.ClientConn.
WithRecvBufferPool any // func (grpc.SharedBufferPool) grpc.DialOption
// RecvBufferPool is implemented by the grpc package and returns a server
// option to configure a shared buffer pool for a grpc.Server.
RecvBufferPool any // func (grpc.SharedBufferPool) grpc.ServerOption
)

View file

@ -20,7 +20,6 @@
import ( import (
"context" "context"
"sync"
"google.golang.org/grpc/internal/buffer" "google.golang.org/grpc/internal/buffer"
) )
@ -38,8 +37,6 @@ type CallbackSerializer struct {
done chan struct{} done chan struct{}
callbacks *buffer.Unbounded callbacks *buffer.Unbounded
closedMu sync.Mutex
closed bool
} }
// NewCallbackSerializer returns a new CallbackSerializer instance. The provided // NewCallbackSerializer returns a new CallbackSerializer instance. The provided
@ -65,56 +62,34 @@ func NewCallbackSerializer(ctx context.Context) *CallbackSerializer {
// callbacks to be executed by the serializer. It is not possible to add // callbacks to be executed by the serializer. It is not possible to add
// callbacks once the context passed to NewCallbackSerializer is cancelled. // callbacks once the context passed to NewCallbackSerializer is cancelled.
func (cs *CallbackSerializer) Schedule(f func(ctx context.Context)) bool { func (cs *CallbackSerializer) Schedule(f func(ctx context.Context)) bool {
cs.closedMu.Lock() return cs.callbacks.Put(f) == nil
defer cs.closedMu.Unlock()
if cs.closed {
return false
}
cs.callbacks.Put(f)
return true
} }
func (cs *CallbackSerializer) run(ctx context.Context) { func (cs *CallbackSerializer) run(ctx context.Context) {
var backlog []func(context.Context)
defer close(cs.done) defer close(cs.done)
// TODO: when Go 1.21 is the oldest supported version, this loop and Close
// can be replaced with:
//
// context.AfterFunc(ctx, cs.callbacks.Close)
for ctx.Err() == nil { for ctx.Err() == nil {
select { select {
case <-ctx.Done(): case <-ctx.Done():
// Do nothing here. Next iteration of the for loop will not happen, // Do nothing here. Next iteration of the for loop will not happen,
// since ctx.Err() would be non-nil. // since ctx.Err() would be non-nil.
case callback, ok := <-cs.callbacks.Get(): case cb := <-cs.callbacks.Get():
if !ok {
return
}
cs.callbacks.Load() cs.callbacks.Load()
callback.(func(ctx context.Context))(ctx) cb.(func(context.Context))(ctx)
} }
} }
// Fetch pending callbacks if any, and execute them before returning from // Close the buffer to prevent new callbacks from being added.
// this method and closing cs.done.
cs.closedMu.Lock()
cs.closed = true
backlog = cs.fetchPendingCallbacks()
cs.callbacks.Close() cs.callbacks.Close()
cs.closedMu.Unlock()
for _, b := range backlog {
b(ctx)
}
}
func (cs *CallbackSerializer) fetchPendingCallbacks() []func(context.Context) { // Run all pending callbacks.
var backlog []func(context.Context) for cb := range cs.callbacks.Get() {
for { cs.callbacks.Load()
select { cb.(func(context.Context))(ctx)
case b := <-cs.callbacks.Get():
backlog = append(backlog, b.(func(context.Context)))
cs.callbacks.Load()
default:
return backlog
}
} }
} }

View file

@ -26,8 +26,6 @@
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"google.golang.org/grpc/grpclog"
) )
// For overriding in unit tests. // For overriding in unit tests.
@ -39,27 +37,12 @@
// and exit from idle mode. // and exit from idle mode.
type Enforcer interface { type Enforcer interface {
ExitIdleMode() error ExitIdleMode() error
EnterIdleMode() error EnterIdleMode()
} }
// Manager defines the functionality required to track RPC activity on a // Manager implements idleness detection and calls the configured Enforcer to
// channel. // enter/exit idle mode when appropriate. Must be created by NewManager.
type Manager interface { type Manager struct {
OnCallBegin() error
OnCallEnd()
Close()
}
type noopManager struct{}
func (noopManager) OnCallBegin() error { return nil }
func (noopManager) OnCallEnd() {}
func (noopManager) Close() {}
// manager implements the Manager interface. It uses atomic operations to
// synchronize access to shared state and a mutex to guarantee mutual exclusion
// in a critical section.
type manager struct {
// State accessed atomically. // State accessed atomically.
lastCallEndTime int64 // Unix timestamp in nanos; time when the most recent RPC completed. lastCallEndTime int64 // Unix timestamp in nanos; time when the most recent RPC completed.
activeCallsCount int32 // Count of active RPCs; -math.MaxInt32 means channel is idle or is trying to get there. activeCallsCount int32 // Count of active RPCs; -math.MaxInt32 means channel is idle or is trying to get there.
@ -69,8 +52,7 @@ type manager struct {
// Can be accessed without atomics or mutex since these are set at creation // Can be accessed without atomics or mutex since these are set at creation
// time and read-only after that. // time and read-only after that.
enforcer Enforcer // Functionality provided by grpc.ClientConn. enforcer Enforcer // Functionality provided by grpc.ClientConn.
timeout int64 // Idle timeout duration nanos stored as an int64. timeout time.Duration
logger grpclog.LoggerV2
// idleMu is used to guarantee mutual exclusion in two scenarios: // idleMu is used to guarantee mutual exclusion in two scenarios:
// - Opposing intentions: // - Opposing intentions:
@ -88,57 +70,48 @@ type manager struct {
timer *time.Timer timer *time.Timer
} }
// ManagerOptions is a collection of options used by
// NewManager.
type ManagerOptions struct {
Enforcer Enforcer
Timeout time.Duration
Logger grpclog.LoggerV2
}
// NewManager creates a new idleness manager implementation for the // NewManager creates a new idleness manager implementation for the
// given idle timeout. // given idle timeout. It begins in idle mode.
func NewManager(opts ManagerOptions) Manager { func NewManager(enforcer Enforcer, timeout time.Duration) *Manager {
if opts.Timeout == 0 { return &Manager{
return noopManager{} enforcer: enforcer,
timeout: timeout,
actuallyIdle: true,
activeCallsCount: -math.MaxInt32,
} }
m := &manager{
enforcer: opts.Enforcer,
timeout: int64(opts.Timeout),
logger: opts.Logger,
}
m.timer = timeAfterFunc(opts.Timeout, m.handleIdleTimeout)
return m
} }
// resetIdleTimer resets the idle timer to the given duration. This method // resetIdleTimerLocked resets the idle timer to the given duration. Called
// should only be called from the timer callback. // when exiting idle mode or when the timer fires and we need to reset it.
func (m *manager) resetIdleTimer(d time.Duration) { func (m *Manager) resetIdleTimerLocked(d time.Duration) {
m.idleMu.Lock() if m.isClosed() || m.timeout == 0 || m.actuallyIdle {
defer m.idleMu.Unlock()
if m.timer == nil {
// Only close sets timer to nil. We are done.
return return
} }
// It is safe to ignore the return value from Reset() because this method is // It is safe to ignore the return value from Reset() because this method is
// only ever called from the timer callback, which means the timer has // only ever called from the timer callback or when exiting idle mode.
// already fired. if m.timer != nil {
m.timer.Reset(d) m.timer.Stop()
}
m.timer = timeAfterFunc(d, m.handleIdleTimeout)
}
func (m *Manager) resetIdleTimer(d time.Duration) {
m.idleMu.Lock()
defer m.idleMu.Unlock()
m.resetIdleTimerLocked(d)
} }
// handleIdleTimeout is the timer callback that is invoked upon expiry of the // handleIdleTimeout is the timer callback that is invoked upon expiry of the
// configured idle timeout. The channel is considered inactive if there are no // configured idle timeout. The channel is considered inactive if there are no
// ongoing calls and no RPC activity since the last time the timer fired. // ongoing calls and no RPC activity since the last time the timer fired.
func (m *manager) handleIdleTimeout() { func (m *Manager) handleIdleTimeout() {
if m.isClosed() { if m.isClosed() {
return return
} }
if atomic.LoadInt32(&m.activeCallsCount) > 0 { if atomic.LoadInt32(&m.activeCallsCount) > 0 {
m.resetIdleTimer(time.Duration(m.timeout)) m.resetIdleTimer(m.timeout)
return return
} }
@ -148,24 +121,12 @@ func (m *manager) handleIdleTimeout() {
// Set the timer to fire after a duration of idle timeout, calculated // Set the timer to fire after a duration of idle timeout, calculated
// from the time the most recent RPC completed. // from the time the most recent RPC completed.
atomic.StoreInt32(&m.activeSinceLastTimerCheck, 0) atomic.StoreInt32(&m.activeSinceLastTimerCheck, 0)
m.resetIdleTimer(time.Duration(atomic.LoadInt64(&m.lastCallEndTime) + m.timeout - time.Now().UnixNano())) m.resetIdleTimer(time.Duration(atomic.LoadInt64(&m.lastCallEndTime)-time.Now().UnixNano()) + m.timeout)
return return
} }
// This CAS operation is extremely likely to succeed given that there has // Now that we've checked that there has been no activity, attempt to enter
// been no activity since the last time we were here. Setting the // idle mode, which is very likely to succeed.
// activeCallsCount to -math.MaxInt32 indicates to OnCallBegin() that the
// channel is either in idle mode or is trying to get there.
if !atomic.CompareAndSwapInt32(&m.activeCallsCount, 0, -math.MaxInt32) {
// This CAS operation can fail if an RPC started after we checked for
// activity at the top of this method, or one was ongoing from before
// the last time we were here. In both case, reset the timer and return.
m.resetIdleTimer(time.Duration(m.timeout))
return
}
// Now that we've set the active calls count to -math.MaxInt32, it's time to
// actually move to idle mode.
if m.tryEnterIdleMode() { if m.tryEnterIdleMode() {
// Successfully entered idle mode. No timer needed until we exit idle. // Successfully entered idle mode. No timer needed until we exit idle.
return return
@ -174,8 +135,7 @@ func (m *manager) handleIdleTimeout() {
// Failed to enter idle mode due to a concurrent RPC that kept the channel // Failed to enter idle mode due to a concurrent RPC that kept the channel
// active, or because of an error from the channel. Undo the attempt to // active, or because of an error from the channel. Undo the attempt to
// enter idle, and reset the timer to try again later. // enter idle, and reset the timer to try again later.
atomic.AddInt32(&m.activeCallsCount, math.MaxInt32) m.resetIdleTimer(m.timeout)
m.resetIdleTimer(time.Duration(m.timeout))
} }
// tryEnterIdleMode instructs the channel to enter idle mode. But before // tryEnterIdleMode instructs the channel to enter idle mode. But before
@ -185,36 +145,49 @@ func (m *manager) handleIdleTimeout() {
// Return value indicates whether or not the channel moved to idle mode. // Return value indicates whether or not the channel moved to idle mode.
// //
// Holds idleMu which ensures mutual exclusion with exitIdleMode. // Holds idleMu which ensures mutual exclusion with exitIdleMode.
func (m *manager) tryEnterIdleMode() bool { func (m *Manager) tryEnterIdleMode() bool {
// Setting the activeCallsCount to -math.MaxInt32 indicates to OnCallBegin()
// that the channel is either in idle mode or is trying to get there.
if !atomic.CompareAndSwapInt32(&m.activeCallsCount, 0, -math.MaxInt32) {
// This CAS operation can fail if an RPC started after we checked for
// activity in the timer handler, or one was ongoing from before the
// last time the timer fired, or if a test is attempting to enter idle
// mode without checking. In all cases, abort going into idle mode.
return false
}
// N.B. if we fail to enter idle mode after this, we must re-add
// math.MaxInt32 to m.activeCallsCount.
m.idleMu.Lock() m.idleMu.Lock()
defer m.idleMu.Unlock() defer m.idleMu.Unlock()
if atomic.LoadInt32(&m.activeCallsCount) != -math.MaxInt32 { if atomic.LoadInt32(&m.activeCallsCount) != -math.MaxInt32 {
// We raced and lost to a new RPC. Very rare, but stop entering idle. // We raced and lost to a new RPC. Very rare, but stop entering idle.
atomic.AddInt32(&m.activeCallsCount, math.MaxInt32)
return false return false
} }
if atomic.LoadInt32(&m.activeSinceLastTimerCheck) == 1 { if atomic.LoadInt32(&m.activeSinceLastTimerCheck) == 1 {
// An very short RPC could have come in (and also finished) after we // A very short RPC could have come in (and also finished) after we
// checked for calls count and activity in handleIdleTimeout(), but // checked for calls count and activity in handleIdleTimeout(), but
// before the CAS operation. So, we need to check for activity again. // before the CAS operation. So, we need to check for activity again.
atomic.AddInt32(&m.activeCallsCount, math.MaxInt32)
return false return false
} }
// No new RPCs have come in since we last set the active calls count value // No new RPCs have come in since we set the active calls count value to
// -math.MaxInt32 in the timer callback. And since we have the lock, it is // -math.MaxInt32. And since we have the lock, it is safe to enter idle mode
// safe to enter idle mode now. // unconditionally now.
if err := m.enforcer.EnterIdleMode(); err != nil { m.enforcer.EnterIdleMode()
m.logger.Errorf("Failed to enter idle mode: %v", err)
return false
}
// Successfully entered idle mode.
m.actuallyIdle = true m.actuallyIdle = true
return true return true
} }
func (m *Manager) EnterIdleModeForTesting() {
m.tryEnterIdleMode()
}
// OnCallBegin is invoked at the start of every RPC. // OnCallBegin is invoked at the start of every RPC.
func (m *manager) OnCallBegin() error { func (m *Manager) OnCallBegin() error {
if m.isClosed() { if m.isClosed() {
return nil return nil
} }
@ -227,7 +200,7 @@ func (m *manager) OnCallBegin() error {
// Channel is either in idle mode or is in the process of moving to idle // Channel is either in idle mode or is in the process of moving to idle
// mode. Attempt to exit idle mode to allow this RPC. // mode. Attempt to exit idle mode to allow this RPC.
if err := m.exitIdleMode(); err != nil { if err := m.ExitIdleMode(); err != nil {
// Undo the increment to calls count, and return an error causing the // Undo the increment to calls count, and return an error causing the
// RPC to fail. // RPC to fail.
atomic.AddInt32(&m.activeCallsCount, -1) atomic.AddInt32(&m.activeCallsCount, -1)
@ -238,28 +211,30 @@ func (m *manager) OnCallBegin() error {
return nil return nil
} }
// exitIdleMode instructs the channel to exit idle mode. // ExitIdleMode instructs m to call the enforcer's ExitIdleMode and update m's
// // internal state.
// Holds idleMu which ensures mutual exclusion with tryEnterIdleMode. func (m *Manager) ExitIdleMode() error {
func (m *manager) exitIdleMode() error { // Holds idleMu which ensures mutual exclusion with tryEnterIdleMode.
m.idleMu.Lock() m.idleMu.Lock()
defer m.idleMu.Unlock() defer m.idleMu.Unlock()
if !m.actuallyIdle { if m.isClosed() || !m.actuallyIdle {
// This can happen in two scenarios: // This can happen in three scenarios:
// - handleIdleTimeout() set the calls count to -math.MaxInt32 and called // - handleIdleTimeout() set the calls count to -math.MaxInt32 and called
// tryEnterIdleMode(). But before the latter could grab the lock, an RPC // tryEnterIdleMode(). But before the latter could grab the lock, an RPC
// came in and OnCallBegin() noticed that the calls count is negative. // came in and OnCallBegin() noticed that the calls count is negative.
// - Channel is in idle mode, and multiple new RPCs come in at the same // - Channel is in idle mode, and multiple new RPCs come in at the same
// time, all of them notice a negative calls count in OnCallBegin and get // time, all of them notice a negative calls count in OnCallBegin and get
// here. The first one to get the lock would got the channel to exit idle. // here. The first one to get the lock would got the channel to exit idle.
// - Channel is not in idle mode, and the user calls Connect which calls
// m.ExitIdleMode.
// //
// Either way, nothing to do here. // In any case, there is nothing to do here.
return nil return nil
} }
if err := m.enforcer.ExitIdleMode(); err != nil { if err := m.enforcer.ExitIdleMode(); err != nil {
return fmt.Errorf("channel failed to exit idle mode: %v", err) return fmt.Errorf("failed to exit idle mode: %w", err)
} }
// Undo the idle entry process. This also respects any new RPC attempts. // Undo the idle entry process. This also respects any new RPC attempts.
@ -267,12 +242,12 @@ func (m *manager) exitIdleMode() error {
m.actuallyIdle = false m.actuallyIdle = false
// Start a new timer to fire after the configured idle timeout. // Start a new timer to fire after the configured idle timeout.
m.timer = timeAfterFunc(time.Duration(m.timeout), m.handleIdleTimeout) m.resetIdleTimerLocked(m.timeout)
return nil return nil
} }
// OnCallEnd is invoked at the end of every RPC. // OnCallEnd is invoked at the end of every RPC.
func (m *manager) OnCallEnd() { func (m *Manager) OnCallEnd() {
if m.isClosed() { if m.isClosed() {
return return
} }
@ -287,15 +262,17 @@ func (m *manager) OnCallEnd() {
atomic.AddInt32(&m.activeCallsCount, -1) atomic.AddInt32(&m.activeCallsCount, -1)
} }
func (m *manager) isClosed() bool { func (m *Manager) isClosed() bool {
return atomic.LoadInt32(&m.closed) == 1 return atomic.LoadInt32(&m.closed) == 1
} }
func (m *manager) Close() { func (m *Manager) Close() {
atomic.StoreInt32(&m.closed, 1) atomic.StoreInt32(&m.closed, 1)
m.idleMu.Lock() m.idleMu.Lock()
m.timer.Stop() if m.timer != nil {
m.timer = nil m.timer.Stop()
m.timer = nil
}
m.idleMu.Unlock() m.idleMu.Unlock()
} }

View file

@ -57,7 +57,7 @@
// GetXDSHandshakeInfoForTesting returns a pointer to the xds.HandshakeInfo // GetXDSHandshakeInfoForTesting returns a pointer to the xds.HandshakeInfo
// stored in the passed in attributes. This is set by // stored in the passed in attributes. This is set by
// credentials/xds/xds.go. // credentials/xds/xds.go.
GetXDSHandshakeInfoForTesting any // func (*attributes.Attributes) *xds.HandshakeInfo GetXDSHandshakeInfoForTesting any // func (*attributes.Attributes) *unsafe.Pointer
// GetServerCredentials returns the transport credentials configured on a // GetServerCredentials returns the transport credentials configured on a
// gRPC server. An xDS-enabled server needs to know what type of credentials // gRPC server. An xDS-enabled server needs to know what type of credentials
// is configured on the underlying gRPC server. This is set by server.go. // is configured on the underlying gRPC server. This is set by server.go.
@ -68,11 +68,11 @@
// This is used in the 1.0 release of gcp/observability, and thus must not be // This is used in the 1.0 release of gcp/observability, and thus must not be
// deleted or changed. // deleted or changed.
CanonicalString any // func (codes.Code) string CanonicalString any // func (codes.Code) string
// DrainServerTransports initiates a graceful close of existing connections // IsRegisteredMethod returns whether the passed in method is registered as
// on a gRPC server accepted on the provided listener address. An // a method on the server.
// xDS-enabled server invokes this method on a grpc.Server when a particular IsRegisteredMethod any // func(*grpc.Server, string) bool
// listener moves to "not-serving" mode. // ServerFromContext returns the server from the context.
DrainServerTransports any // func(*grpc.Server, string) ServerFromContext any // func(context.Context) *grpc.Server
// AddGlobalServerOptions adds an array of ServerOption that will be // AddGlobalServerOptions adds an array of ServerOption that will be
// effective globally for newly created servers. The priority will be: 1. // effective globally for newly created servers. The priority will be: 1.
// user-provided; 2. this method; 3. default values. // user-provided; 2. this method; 3. default values.
@ -177,10 +177,25 @@
GRPCResolverSchemeExtraMetadata string = "xds" GRPCResolverSchemeExtraMetadata string = "xds"
// EnterIdleModeForTesting gets the ClientConn to enter IDLE mode. // EnterIdleModeForTesting gets the ClientConn to enter IDLE mode.
EnterIdleModeForTesting any // func(*grpc.ClientConn) error EnterIdleModeForTesting any // func(*grpc.ClientConn)
// ExitIdleModeForTesting gets the ClientConn to exit IDLE mode. // ExitIdleModeForTesting gets the ClientConn to exit IDLE mode.
ExitIdleModeForTesting any // func(*grpc.ClientConn) error ExitIdleModeForTesting any // func(*grpc.ClientConn) error
ChannelzTurnOffForTesting func()
// TriggerXDSResourceNameNotFoundForTesting triggers the resource-not-found
// error for a given resource type and name. This is usually triggered when
// the associated watch timer fires. For testing purposes, having this
// function makes events more predictable than relying on timer events.
TriggerXDSResourceNameNotFoundForTesting any // func(func(xdsresource.Type, string), string, string) error
// TriggerXDSResourceNotFoundClient invokes the testing xDS Client singleton
// to invoke resource not found for a resource type name and resource name.
TriggerXDSResourceNameNotFoundClient any // func(string, string) error
// FromOutgoingContextRaw returns the un-merged, intermediary contents of metadata.rawMD.
FromOutgoingContextRaw any // func(context.Context) (metadata.MD, [][]string, bool)
) )
// HealthChecker defines the signature of the client-side LB channel health checking function. // HealthChecker defines the signature of the client-side LB channel health checking function.

View file

@ -23,7 +23,6 @@
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"os" "os"
@ -37,6 +36,7 @@
"google.golang.org/grpc/internal/backoff" "google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/internal/resolver/dns/internal"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig" "google.golang.org/grpc/serviceconfig"
) )
@ -47,15 +47,11 @@
var logger = grpclog.Component("dns") var logger = grpclog.Component("dns")
// Globals to stub out in tests. TODO: Perhaps these two can be combined into a
// single variable for testing the resolver?
var (
newTimer = time.NewTimer
newTimerDNSResRate = time.NewTimer
)
func init() { func init() {
resolver.Register(NewBuilder()) resolver.Register(NewBuilder())
internal.TimeAfterFunc = time.After
internal.NewNetResolver = newNetResolver
internal.AddressDialer = addressDialer
} }
const ( const (
@ -70,23 +66,6 @@ func init() {
txtAttribute = "grpc_config=" txtAttribute = "grpc_config="
) )
var (
errMissingAddr = errors.New("dns resolver: missing address")
// Addresses ending with a colon that is supposed to be the separator
// between host and port is not allowed. E.g. "::" is a valid address as
// it is an IPv6 address (host only) and "[::]:" is invalid as it ends with
// a colon as the host and port separator
errEndsWithColon = errors.New("dns resolver: missing port after port-separator colon")
)
var (
defaultResolver netResolver = net.DefaultResolver
// To prevent excessive re-resolution, we enforce a rate limit on DNS
// resolution requests.
minDNSResRate = 30 * time.Second
)
var addressDialer = func(address string) func(context.Context, string, string) (net.Conn, error) { var addressDialer = func(address string) func(context.Context, string, string) (net.Conn, error) {
return func(ctx context.Context, network, _ string) (net.Conn, error) { return func(ctx context.Context, network, _ string) (net.Conn, error) {
var dialer net.Dialer var dialer net.Dialer
@ -94,7 +73,11 @@ func init() {
} }
} }
var newNetResolver = func(authority string) (netResolver, error) { var newNetResolver = func(authority string) (internal.NetResolver, error) {
if authority == "" {
return net.DefaultResolver, nil
}
host, port, err := parseTarget(authority, defaultDNSSvrPort) host, port, err := parseTarget(authority, defaultDNSSvrPort)
if err != nil { if err != nil {
return nil, err return nil, err
@ -104,7 +87,7 @@ func init() {
return &net.Resolver{ return &net.Resolver{
PreferGo: true, PreferGo: true,
Dial: addressDialer(authorityWithPort), Dial: internal.AddressDialer(authorityWithPort),
}, nil }, nil
} }
@ -142,13 +125,9 @@ func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts
disableServiceConfig: opts.DisableServiceConfig, disableServiceConfig: opts.DisableServiceConfig,
} }
if target.URL.Host == "" { d.resolver, err = internal.NewNetResolver(target.URL.Host)
d.resolver = defaultResolver if err != nil {
} else { return nil, err
d.resolver, err = newNetResolver(target.URL.Host)
if err != nil {
return nil, err
}
} }
d.wg.Add(1) d.wg.Add(1)
@ -161,12 +140,6 @@ func (b *dnsBuilder) Scheme() string {
return "dns" return "dns"
} }
type netResolver interface {
LookupHost(ctx context.Context, host string) (addrs []string, err error)
LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error)
LookupTXT(ctx context.Context, name string) (txts []string, err error)
}
// deadResolver is a resolver that does nothing. // deadResolver is a resolver that does nothing.
type deadResolver struct{} type deadResolver struct{}
@ -178,7 +151,7 @@ func (deadResolver) Close() {}
type dnsResolver struct { type dnsResolver struct {
host string host string
port string port string
resolver netResolver resolver internal.NetResolver
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
cc resolver.ClientConn cc resolver.ClientConn
@ -223,29 +196,27 @@ func (d *dnsResolver) watcher() {
err = d.cc.UpdateState(*state) err = d.cc.UpdateState(*state)
} }
var timer *time.Timer var waitTime time.Duration
if err == nil { if err == nil {
// Success resolving, wait for the next ResolveNow. However, also wait 30 // Success resolving, wait for the next ResolveNow. However, also wait 30
// seconds at the very least to prevent constantly re-resolving. // seconds at the very least to prevent constantly re-resolving.
backoffIndex = 1 backoffIndex = 1
timer = newTimerDNSResRate(minDNSResRate) waitTime = internal.MinResolutionRate
select { select {
case <-d.ctx.Done(): case <-d.ctx.Done():
timer.Stop()
return return
case <-d.rn: case <-d.rn:
} }
} else { } else {
// Poll on an error found in DNS Resolver or an error received from // Poll on an error found in DNS Resolver or an error received from
// ClientConn. // ClientConn.
timer = newTimer(backoff.DefaultExponential.Backoff(backoffIndex)) waitTime = backoff.DefaultExponential.Backoff(backoffIndex)
backoffIndex++ backoffIndex++
} }
select { select {
case <-d.ctx.Done(): case <-d.ctx.Done():
timer.Stop()
return return
case <-timer.C: case <-internal.TimeAfterFunc(waitTime):
} }
} }
} }
@ -387,7 +358,7 @@ func formatIP(addr string) (addrIP string, ok bool) {
// target: ":80" defaultPort: "443" returns host: "localhost", port: "80" // target: ":80" defaultPort: "443" returns host: "localhost", port: "80"
func parseTarget(target, defaultPort string) (host, port string, err error) { func parseTarget(target, defaultPort string) (host, port string, err error) {
if target == "" { if target == "" {
return "", "", errMissingAddr return "", "", internal.ErrMissingAddr
} }
if ip := net.ParseIP(target); ip != nil { if ip := net.ParseIP(target); ip != nil {
// target is an IPv4 or IPv6(without brackets) address // target is an IPv4 or IPv6(without brackets) address
@ -397,7 +368,7 @@ func parseTarget(target, defaultPort string) (host, port string, err error) {
if port == "" { if port == "" {
// If the port field is empty (target ends with colon), e.g. "[::1]:", // If the port field is empty (target ends with colon), e.g. "[::1]:",
// this is an error. // this is an error.
return "", "", errEndsWithColon return "", "", internal.ErrEndsWithColon
} }
// target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port // target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port
if host == "" { if host == "" {

Some files were not shown because too many files have changed in this diff Show more