mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-08-30 12:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2908b43089 | ||
|
|
9ba1aa1c43 | ||
|
|
8dd18a45a0 | ||
|
|
4a0d41a96a | ||
|
|
a8c092f8e1 | ||
|
|
d4ed42c71f | ||
|
|
0e8dd36ac3 | ||
|
|
6b3276a0f7 | ||
|
|
550923c2c3 | ||
|
|
bb35cf9614 | ||
|
|
658b6cd30a |
@@ -17,9 +17,6 @@ jobs:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -41,7 +38,6 @@ jobs:
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
exit-code: '0'
|
||||
|
||||
@@ -297,7 +297,7 @@ Balance Plugins: These plugins process all pods, or groups of pods, and determin
|
||||
| [RemovePodsViolatingNodeTaints](#removepodsviolatingnodetaints) |Deschedule|Evicts pods violating node taints|
|
||||
| [RemovePodsViolatingTopologySpreadConstraint](#removepodsviolatingtopologyspreadconstraint) |Balance|Evicts pods violating TopologySpreadConstraints|
|
||||
| [RemovePodsHavingTooManyRestarts](#removepodshavingtoomanyrestarts) |Deschedule|Evicts pods having too many restarts|
|
||||
| [PodLifeTime](#podlifetime) |Deschedule|Evicts pods that have exceeded a specified age limit|
|
||||
| [PodLifeTime](#podlifetime) |Deschedule|Evicts pods based on age, status transitions, conditions, states, exit codes, and owner kinds|
|
||||
| [RemoveFailedPods](#removefailedpods) |Deschedule|Evicts pods with certain failed reasons and exit codes|
|
||||
|
||||
|
||||
@@ -785,30 +785,52 @@ profiles:
|
||||
|
||||
### PodLifeTime
|
||||
|
||||
This strategy evicts pods that are older than `maxPodLifeTimeSeconds`.
|
||||
This strategy evicts pods based on their age, status transitions, conditions, states, exit codes, and owner kinds. It supports both simple age-based eviction and fine-grained cleanup of pods matching specific transition criteria.
|
||||
|
||||
You can also specify `states` parameter to **only** evict pods matching the following conditions:
|
||||
> The primary purpose for using states like `Succeeded` and `Failed` is releasing resources so that new pods can be rescheduled.
|
||||
> I.e., the main motivation is not for cleaning pods, rather to release resources.
|
||||
- [Pod Phase](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase) status of: `Running`, `Pending`, `Succeeded`, `Failed`, `Unknown`
|
||||
- [Pod Reason](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions) reasons of: `NodeAffinity`, `NodeLost`, `Shutdown`, `UnexpectedAdmissionError`
|
||||
- [Container State Waiting](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-state-waiting) condition of: `PodInitializing`, `ContainerCreating`, `ImagePullBackOff`, `CrashLoopBackOff`, `CreateContainerConfigError`, `ErrImagePull`, `ImagePullBackOff`, `CreateContainerError`, `InvalidImageName`
|
||||
All non-empty filter categories are ANDed (a pod must satisfy every specified filter). Within each category, items are ORed (matching any one entry satisfies that filter). For `conditions`, a pod is eligible for eviction if **any** of the listed condition filters match — each filter is evaluated independently against the pod's `status.conditions[]` entries. Pods are processed from oldest to newest based on their creation time.
|
||||
|
||||
If a value for `states` or `podStatusPhases` is not specified,
|
||||
Pods in any state (even `Running`) are considered for eviction.
|
||||
See the [plugin README](pkg/framework/plugins/podlifetime/README.md) for detailed documentation and advanced use cases.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Notes |
|
||||
|--------------------------------|---------------------------------------------------|--------------------------|
|
||||
| `maxPodLifeTimeSeconds` | int | |
|
||||
| `states` | list(string) | Only supported in v0.25+ |
|
||||
| `includingInitContainers` | bool | Only supported in v0.31+ |
|
||||
| `includingEphemeralContainers` | bool | Only supported in v0.31+ |
|
||||
| `namespaces` | (see [namespace filtering](#namespace-filtering)) | |
|
||||
| `labelSelector` | (see [label filtering](#label-filtering)) | |
|
||||
| Name | Type | Notes |
|
||||
|------|------|-------|
|
||||
| `conditions` | list(object) | Each with optional `type`, `status`, `reason`, `minTimeSinceLastTransitionSeconds` fields |
|
||||
| `exitCodes` | list(int32) | Container terminated exit codes |
|
||||
| `includingEphemeralContainers` | bool | Extend state filtering to ephemeral containers |
|
||||
| `includingInitContainers` | bool | Extend state/exitCode filtering to init containers |
|
||||
| `labelSelector` | (see [label filtering](#label-filtering)) | |
|
||||
| `maxPodLifeTimeSeconds` | uint | Pods older than this many seconds are evicted |
|
||||
| `namespaces` | (see [namespace filtering](#namespace-filtering)) | |
|
||||
| `ownerKinds` | object | `include` or `exclude` list of owner reference kinds |
|
||||
| `states` | list(string) | Pod phases, pod status reasons, container waiting/terminated reasons |
|
||||
|
||||
**Example:**
|
||||
**Example (transition-based eviction):**
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
kind: "DeschedulerPolicy"
|
||||
profiles:
|
||||
- name: ProfileName
|
||||
pluginConfig:
|
||||
- name: "PodLifeTime"
|
||||
args:
|
||||
states:
|
||||
- "Succeeded"
|
||||
conditions:
|
||||
- reason: "PodCompleted"
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400
|
||||
ownerKinds:
|
||||
exclude:
|
||||
- "Job"
|
||||
plugins:
|
||||
deschedule:
|
||||
enabled:
|
||||
- "PodLifeTime"
|
||||
```
|
||||
|
||||
**Example (age-based eviction):**
|
||||
|
||||
```yaml
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
@@ -829,6 +851,7 @@ profiles:
|
||||
```
|
||||
|
||||
### RemoveFailedPods
|
||||
|
||||
This strategy evicts pods that are in failed status phase.
|
||||
You can provide optional parameters to filter by failed pods' and containters' `reasons`. and `exitCodes`. `exitCodes` apply to failed pods' containers with `terminated` state only. `reasons` and `exitCodes` can be expanded to include those of InitContainers as well by setting the optional parameter `includingInitContainers` to `true`.
|
||||
You can specify an optional parameter `minPodLifetimeSeconds` to evict pods that are older than specified seconds.
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords:
|
||||
- descheduler
|
||||
- kube-scheduler
|
||||
home: https://github.com/kubernetes-sigs/descheduler
|
||||
icon: https://kubernetes.io/images/favicon.png
|
||||
icon: https://raw.githubusercontent.com/kubernetes-sigs/descheduler/master/assets/logo/descheduler-stacked-color.png
|
||||
sources:
|
||||
- https://github.com/kubernetes-sigs/descheduler
|
||||
maintainers:
|
||||
|
||||
@@ -36,6 +36,9 @@ rules:
|
||||
resourceNames: ["{{ .Values.leaderElection.resourceName | default "descheduler" }}"]
|
||||
verbs: ["get", "patch", "delete"]
|
||||
{{- end }}
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
{{- if and .Values.deschedulerPolicy }}
|
||||
{{- range .Values.deschedulerPolicy.metricsProviders }}
|
||||
{{- if and (hasKey . "source") (eq .source "KubernetesMetrics") }}
|
||||
|
||||
@@ -96,6 +96,10 @@ spec:
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.initContainers }}
|
||||
initContainers:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "v%s" .Chart.AppVersion) }}"
|
||||
|
||||
@@ -49,6 +49,10 @@ spec:
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .Values.initContainers }}
|
||||
initContainers:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "v%s" .Chart.AppVersion) }}"
|
||||
|
||||
@@ -233,6 +233,13 @@ livenessProbe:
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 5
|
||||
|
||||
# Init containers to run before the main descheduler container
|
||||
initContainers: []
|
||||
# Example:
|
||||
# - name: init-myservice
|
||||
# image: busybox:1.28
|
||||
# command: ['sh', '-c', 'echo The app is running! && sleep 10']
|
||||
|
||||
service:
|
||||
enabled: false
|
||||
# @param service.ipFamilyPolicy [string], support SingleStack, PreferDualStack and RequireDualStack
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: "descheduler/v1alpha2"
|
||||
kind: "DeschedulerPolicy"
|
||||
profiles:
|
||||
- name: ProfileName
|
||||
pluginConfig:
|
||||
- name: "PodLifeTime"
|
||||
args:
|
||||
states:
|
||||
- "Succeeded"
|
||||
conditions:
|
||||
- reason: "PodCompleted"
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400 # 4 hours
|
||||
namespaces:
|
||||
include:
|
||||
- "default"
|
||||
plugins:
|
||||
deschedule:
|
||||
enabled:
|
||||
- "PodLifeTime"
|
||||
@@ -13,13 +13,13 @@ require (
|
||||
github.com/prometheus/common v0.67.5
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
go.opentelemetry.io/otel v1.40.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.40.0
|
||||
go.opentelemetry.io/otel v1.41.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0
|
||||
go.opentelemetry.io/otel/sdk v1.41.0
|
||||
go.opentelemetry.io/otel/trace v1.41.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/grpc v1.79.1
|
||||
k8s.io/api v0.35.1
|
||||
k8s.io/apimachinery v0.35.1
|
||||
k8s.io/apiserver v0.35.1
|
||||
@@ -37,7 +37,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
@@ -74,7 +74,7 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
@@ -98,26 +98,26 @@ require (
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -171,6 +173,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
@@ -357,22 +361,39 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0 h1:w/o339tDd6Qtu3+ytwt+/jon2yjAs3Ot8Xq8pelfhSo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0/go.mod h1:pdhNtM9C4H5fRdrnwO7NjxzQWhKSSxCHk/KluVqDVC0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0/go.mod h1:yk5LXEYhsL2htyDNJbEq7fWzNEigeEdV5xBF/Y+kAv0=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -408,6 +429,8 @@ golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+Zdx
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
@@ -435,6 +458,8 @@ golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -480,9 +505,13 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -550,6 +579,8 @@ golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4=
|
||||
@@ -578,6 +609,8 @@ golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -600,6 +633,8 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -636,6 +671,8 @@ golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
@@ -655,13 +692,19 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
@@ -108,6 +108,8 @@ func (mc *MetricsCollector) NodeUsage(node *v1.Node) (api.ReferencedResourceList
|
||||
}
|
||||
|
||||
func (mc *MetricsCollector) HasSynced() bool {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
return mc.hasSynced
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,28 @@ func SortPodsBasedOnAge(pods []*v1.Pod) {
|
||||
})
|
||||
}
|
||||
|
||||
// HasMatchingContainerWaitingState returns true if any container status has a
|
||||
// waiting reason present in the given set.
|
||||
func HasMatchingContainerWaitingState(statuses []v1.ContainerStatus, states sets.Set[string]) bool {
|
||||
for _, cs := range statuses {
|
||||
if cs.State.Waiting != nil && states.Has(cs.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasMatchingContainerTerminatedState returns true if any container status has
|
||||
// a terminated reason present in the given set.
|
||||
func HasMatchingContainerTerminatedState(statuses []v1.ContainerStatus, states sets.Set[string]) bool {
|
||||
for _, cs := range statuses {
|
||||
if cs.State.Terminated != nil && states.Has(cs.State.Terminated.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GroupByNodeName(pods []*v1.Pod) map[string][]*v1.Pod {
|
||||
m := make(map[string][]*v1.Pod)
|
||||
for i := 0; i < len(pods); i++ {
|
||||
|
||||
@@ -260,10 +260,12 @@ func TestPromClientControllerSync_ClientCreation(t *testing.T) {
|
||||
ctrl := setupMode.setupFn(ctx, t, tc.objects)
|
||||
|
||||
// Set additional test-specific fields
|
||||
ctrl.mu.Lock()
|
||||
ctrl.currentPrometheusAuthToken = tc.currentAuthToken
|
||||
if tc.currentAuthToken != "" {
|
||||
ctrl.previousPrometheusClientTransport = &http.Transport{}
|
||||
}
|
||||
ctrl.mu.Unlock()
|
||||
|
||||
// Mock createPrometheusClient
|
||||
clientCreated := false
|
||||
@@ -302,12 +304,17 @@ func TestPromClientControllerSync_ClientCreation(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify token cleared expectations
|
||||
if tc.expectCurrentTokenCleared && ctrl.currentPrometheusAuthToken != "" {
|
||||
ctrl.mu.RLock()
|
||||
currentToken := ctrl.currentPrometheusAuthToken
|
||||
previousTransport := ctrl.previousPrometheusClientTransport
|
||||
ctrl.mu.RUnlock()
|
||||
|
||||
if tc.expectCurrentTokenCleared && currentToken != "" {
|
||||
t.Errorf("Expected current auth token to be cleared but it wasn't")
|
||||
}
|
||||
|
||||
// Verify previous transport cleared expectations
|
||||
if tc.expectPreviousTransportCleared && ctrl.previousPrometheusClientTransport != nil {
|
||||
if tc.expectPreviousTransportCleared && previousTransport != nil {
|
||||
t.Errorf("Expected previous transport to be cleared but it wasn't")
|
||||
}
|
||||
|
||||
@@ -320,8 +327,11 @@ func TestPromClientControllerSync_ClientCreation(t *testing.T) {
|
||||
if tc.expectClientCreated && len(tc.objects) > 0 {
|
||||
if secret, ok := tc.objects[0].(*v1.Secret); ok && secret.Data != nil {
|
||||
expectedToken := string(secret.Data[prometheusAuthTokenSecretKey])
|
||||
if ctrl.currentPrometheusAuthToken != expectedToken {
|
||||
t.Errorf("Expected current auth token to be %q but got %q", expectedToken, ctrl.currentPrometheusAuthToken)
|
||||
ctrl.mu.RLock()
|
||||
actualToken := ctrl.currentPrometheusAuthToken
|
||||
ctrl.mu.RUnlock()
|
||||
if actualToken != expectedToken {
|
||||
t.Errorf("Expected current auth token to be %q but got %q", expectedToken, actualToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,12 +578,17 @@ func TestPromClientControllerSync_EventHandler(t *testing.T) {
|
||||
t.Errorf("Expected %d clients created, but got %d", tc.expectedCreatedClientsCount, len(createdClients))
|
||||
}
|
||||
|
||||
if ctrl.currentPrometheusAuthToken != tc.expectedCurrentToken {
|
||||
t.Errorf("Expected current token to be %q, got %q", tc.expectedCurrentToken, ctrl.currentPrometheusAuthToken)
|
||||
ctrl.mu.RLock()
|
||||
actualToken := ctrl.currentPrometheusAuthToken
|
||||
actualTransport := ctrl.previousPrometheusClientTransport
|
||||
ctrl.mu.RUnlock()
|
||||
|
||||
if actualToken != tc.expectedCurrentToken {
|
||||
t.Errorf("Expected current token to be %q, got %q", tc.expectedCurrentToken, actualToken)
|
||||
}
|
||||
|
||||
if tc.expectedPreviousTransportCleared {
|
||||
if ctrl.previousPrometheusClientTransport != nil {
|
||||
if actualTransport != nil {
|
||||
t.Error("Expected previous transport to be cleared, but it was set")
|
||||
}
|
||||
}
|
||||
@@ -723,9 +738,10 @@ func TestReconcileInClusterSAToken(t *testing.T) {
|
||||
}
|
||||
_, descheduler, _, _ := initDescheduler(t, ctx, initFeatureGates(), deschedulerPolicy, nil, false)
|
||||
|
||||
// Override the fields needed for this test (no need for a mutex
|
||||
// since reconcileInClusterSAToken gets to run later)
|
||||
// Override the fields needed for this test
|
||||
descheduler.inClusterPromClientCtrl.mu.Lock()
|
||||
descheduler.inClusterPromClientCtrl.currentPrometheusAuthToken = tc.currentAuthToken
|
||||
descheduler.inClusterPromClientCtrl.mu.Unlock()
|
||||
descheduler.inClusterPromClientCtrl.inClusterConfig = tc.inClusterConfigFunc
|
||||
return descheduler.inClusterPromClientCtrl
|
||||
},
|
||||
@@ -735,12 +751,14 @@ func TestReconcileInClusterSAToken(t *testing.T) {
|
||||
ctrl := setupMode.init(t)
|
||||
|
||||
// Set previous transport and client if test expects them to be cleared
|
||||
ctrl.mu.Lock()
|
||||
if tc.expectPreviousTransportCleared {
|
||||
ctrl.previousPrometheusClientTransport = &http.Transport{}
|
||||
}
|
||||
if tc.expectPromClientCleared {
|
||||
ctrl.promClient = &mockPrometheusClient{name: "old-client"}
|
||||
}
|
||||
ctrl.mu.Unlock()
|
||||
|
||||
// Mock createPrometheusClient
|
||||
clientCreated := false
|
||||
@@ -779,20 +797,25 @@ func TestReconcileInClusterSAToken(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify token expectations
|
||||
if ctrl.currentPrometheusAuthToken != tc.expectCurrentToken {
|
||||
t.Errorf("Expected current token to be %q but got %q", tc.expectCurrentToken, ctrl.currentPrometheusAuthToken)
|
||||
ctrl.mu.RLock()
|
||||
actualToken := ctrl.currentPrometheusAuthToken
|
||||
actualTransport := ctrl.previousPrometheusClientTransport
|
||||
ctrl.mu.RUnlock()
|
||||
|
||||
if actualToken != tc.expectCurrentToken {
|
||||
t.Errorf("Expected current token to be %q but got %q", tc.expectCurrentToken, actualToken)
|
||||
}
|
||||
|
||||
// Verify previous transport cleared when expected
|
||||
if tc.expectPreviousTransportCleared {
|
||||
if tc.expectClientCreated {
|
||||
// Success case: new transport should be set
|
||||
if ctrl.previousPrometheusClientTransport == nil {
|
||||
if actualTransport == nil {
|
||||
t.Error("Expected previous transport to be set to new transport, but it was nil")
|
||||
}
|
||||
} else if tc.expectedErr != nil {
|
||||
// Failure case: transport should be nil
|
||||
if ctrl.previousPrometheusClientTransport != nil {
|
||||
if actualTransport != nil {
|
||||
t.Error("Expected previous transport to be cleared on error, but it was set")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,136 +2,116 @@
|
||||
|
||||
## What It Does
|
||||
|
||||
The PodLifeTime plugin evicts pods that have been running for too long. You can configure a maximum age threshold, and the plugin evicts pods older than that threshold. The oldest pods are evicted first.
|
||||
The PodLifeTime plugin evicts pods based on their age, status phase, condition transitions, container states, exit codes, and owner kinds. It can be used for simple age-based eviction or for fine-grained cleanup of pods matching specific transition criteria.
|
||||
|
||||
## How It Works
|
||||
|
||||
The plugin examines all pods across your nodes and selects those that exceed the configured age threshold. You can further narrow down which pods are considered by specifying:
|
||||
The plugin builds a filter chain from the configured criteria. All non-empty filter categories are ANDed together (a pod must satisfy every specified filter to be evicted). Within each filter category, items are ORed (matching any one entry satisfies that filter).
|
||||
|
||||
- Which namespaces to include or exclude
|
||||
- Which labels pods must have
|
||||
- Which states pods must be in (e.g., Running, Pending, CrashLoopBackOff)
|
||||
|
||||
Once pods are selected, they are sorted by age (oldest first) and evicted in that order. Eviction stops when limits are reached (per-node limits, total limits, or Pod Disruption Budget constraints).
|
||||
Once pods are selected, they are sorted by their creation time with the oldest first, then evicted in order. Eviction stops when limits are reached (per-node limits, total limits, or Pod Disruption Budget constraints).
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Resource Leakage Mitigation**: Restart long-running pods that may have accumulated memory leaks, stale cache, or resource leaks
|
||||
- **Evict completed/succeeded pods that have been idle too long**:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 604800 # 7 days
|
||||
states: [Running]
|
||||
states: [Succeeded]
|
||||
conditions:
|
||||
- reason: PodCompleted
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400 # 4 hours
|
||||
```
|
||||
|
||||
- **Ephemeral Workload Cleanup**: Remove long-running batch jobs, test pods, or temporary workloads that have exceeded their expected lifetime
|
||||
- **Evict failed pods**, excluding Job-owned pods:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 7200 # 2 hours
|
||||
states: [Succeeded, Failed]
|
||||
```
|
||||
|
||||
- **Node Hygiene**: Remove forgotten or stuck pods that are consuming resources but not making progress
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 3600 # 1 hour
|
||||
states: [CrashLoopBackOff, ImagePullBackOff, ErrImagePull]
|
||||
states: [Failed]
|
||||
exitCodes: [1]
|
||||
ownerKinds:
|
||||
exclude: [Job]
|
||||
maxPodLifeTimeSeconds: 3600
|
||||
includingInitContainers: true
|
||||
```
|
||||
|
||||
- **Config/Secret Update Pickup**: Force pod restart to pick up updated ConfigMaps, Secrets, or environment variables
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 86400 # 1 day
|
||||
states: [Running]
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
config-refresh: enabled
|
||||
```
|
||||
|
||||
- **Security Rotation**: Periodically refresh pods to pick up new security tokens, certificates, or patched container images
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 259200 # 3 days
|
||||
states: [Running]
|
||||
namespaces:
|
||||
exclude: [kube-system]
|
||||
```
|
||||
|
||||
- **Dev/Test Environment Cleanup**: Automatically clean up old pods in development or staging namespaces
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 86400 # 1 day
|
||||
namespaces:
|
||||
include: [dev, staging, test]
|
||||
```
|
||||
|
||||
- **Cluster Health Freshness**: Ensure pods periodically restart to maintain cluster health and verify workloads can recover from restarts
|
||||
- **Resource Leakage Mitigation**: Restart long-running pods that may have accumulated memory leaks:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 604800 # 7 days
|
||||
states: [Running]
|
||||
namespaces:
|
||||
exclude: [kube-system, production]
|
||||
```
|
||||
|
||||
- **Rebalancing Assistance**: Work alongside other descheduler strategies by removing old pods to allow better pod distribution
|
||||
- **Clean up stuck pods in CrashLoopBackOff**:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 1209600 # 14 days
|
||||
states: [Running]
|
||||
states: [CrashLoopBackOff, ImagePullBackOff]
|
||||
```
|
||||
|
||||
- **Non-Critical Stateful Refresh**: Occasionally reset tolerable stateful workloads that can handle data loss or have external backup mechanisms
|
||||
- **Evict pods owned only by specific kinds**:
|
||||
```yaml
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 2592000 # 30 days
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
stateful-tier: cache
|
||||
states: [Succeeded, Failed]
|
||||
ownerKinds:
|
||||
include: [Job]
|
||||
maxPodLifeTimeSeconds: 600
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Parameter | Description | Type | Required | Default |
|
||||
|-----------|-------------|------|----------|---------|
|
||||
| `maxPodLifeTimeSeconds` | Pods older than this many seconds are evicted | `uint` | Yes | - |
|
||||
| `namespaces` | Limit eviction to specific namespaces (or exclude specific namespaces) | `Namespaces` | No | `nil` |
|
||||
| `maxPodLifeTimeSeconds` | Pods older than this many seconds are evicted | `uint` | No* | `nil` |
|
||||
| `states` | Filter pods by phase, pod status reason, or container waiting/terminated reason. A pod matches if any of its state values appear in this list | `[]string` | No | `nil` |
|
||||
| `conditions` | Only evict pods with matching status conditions (see PodConditionFilter) | `[]PodConditionFilter` | No | `nil` |
|
||||
| `exitCodes` | Only evict pods with matching container terminated exit codes | `[]int32` | No | `nil` |
|
||||
| `ownerKinds` | Include or exclude pods by owner reference kind | `OwnerKinds` | No | `nil` |
|
||||
| `namespaces` | Limit eviction to specific namespaces (include or exclude) | `Namespaces` | No | `nil` |
|
||||
| `labelSelector` | Only evict pods matching these labels | `metav1.LabelSelector` | No | `nil` |
|
||||
| `states` | Only evict pods in specific states (e.g., Running, CrashLoopBackOff) | `[]string` | No | `nil` |
|
||||
| `includingInitContainers` | When checking states, also check init container states | `bool` | No | `false` |
|
||||
| `includingEphemeralContainers` | When checking states, also check ephemeral container states | `bool` | No | `false` |
|
||||
| `includingInitContainers` | Extend state/exitCode filtering to init containers | `bool` | No | `false` |
|
||||
| `includingEphemeralContainers` | Extend state filtering to ephemeral containers | `bool` | No | `false` |
|
||||
|
||||
### Discovering states
|
||||
*At least one filtering criterion must be specified (`maxPodLifeTimeSeconds`, `states`, `conditions`, or `exitCodes`).
|
||||
|
||||
Each pod is checked for the following locations to discover its relevant state:
|
||||
### States
|
||||
|
||||
1. **Pod Phase** - The overall pod lifecycle phase:
|
||||
- `Running` - Pod is running on a node
|
||||
- `Pending` - Pod has been accepted but containers are not yet running
|
||||
- `Succeeded` - All containers terminated successfully
|
||||
- `Failed` - All containers terminated, at least one failed
|
||||
- `Unknown` - Pod state cannot be determined
|
||||
The `states` field matches pods using an OR across these categories:
|
||||
|
||||
2. **Pod Status Reason** - Why the pod is in its current state:
|
||||
- `NodeAffinity` - Pod cannot be scheduled due to node affinity rules
|
||||
- `NodeLost` - Node hosting the pod is lost
|
||||
- `Shutdown` - Pod terminated due to node shutdown
|
||||
- `UnexpectedAdmissionError` - Pod admission failed unexpectedly
|
||||
| Category | Examples |
|
||||
|----------|----------|
|
||||
| Pod phase | `Running`, `Pending`, `Succeeded`, `Failed`, `Unknown` |
|
||||
| Pod status reason | `NodeAffinity`, `NodeLost`, `Shutdown`, `UnexpectedAdmissionError` |
|
||||
| Container waiting reason | `CrashLoopBackOff`, `ImagePullBackOff`, `ErrImagePull`, `CreateContainerConfigError`, `CreateContainerError`, `InvalidImageName`, `PodInitializing`, `ContainerCreating` |
|
||||
| Container terminated reason | `OOMKilled`, `Error`, `Completed`, `DeadlineExceeded`, `Evicted`, `ContainerCannotRun`, `StartError` |
|
||||
|
||||
3. **Container Waiting Reason** - Why containers are waiting to start:
|
||||
- `PodInitializing` - Pod is still initializing
|
||||
- `ContainerCreating` - Container is being created
|
||||
- `ImagePullBackOff` - Image pull is failing and backing off
|
||||
- `CrashLoopBackOff` - Container is crashing repeatedly
|
||||
- `CreateContainerConfigError` - Container configuration is invalid
|
||||
- `ErrImagePull` - Image cannot be pulled
|
||||
- `CreateContainerError` - Container creation failed
|
||||
- `InvalidImageName` - Image name is invalid
|
||||
When `includingInitContainers` is true, init container states are also checked. When `includingEphemeralContainers` is true, ephemeral container states are also checked.
|
||||
|
||||
By default, only regular containers are checked. Enable `includingInitContainers` or `includingEphemeralContainers` to also check those container types.
|
||||
### PodConditionFilter
|
||||
|
||||
Each condition filter matches against `pod.status.conditions[]` entries. Within a single filter, all specified field-level checks must match (AND). Unset fields are not checked. Across the list, condition filters are ORed — a pod is eligible for eviction if **any** of the listed condition filters match.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `type` | Condition type (e.g., `Ready`, `Initialized`, `ContainersReady`) |
|
||||
| `status` | Condition status (`True`, `False`, `Unknown`) |
|
||||
| `reason` | Condition reason (e.g., `PodCompleted`) |
|
||||
| `minTimeSinceLastTransitionSeconds` | Require the matching condition's `lastTransitionTime` to be at least this many seconds in the past |
|
||||
|
||||
At least one of these fields must be set per filter entry.
|
||||
|
||||
When `minTimeSinceLastTransitionSeconds` is set on a filter, a pod's condition must both match the type/status/reason fields AND have transitioned long enough ago. If the condition has no `lastTransitionTime`, it does not match.
|
||||
|
||||
### OwnerKinds
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `include` | Only evict pods owned by these kinds |
|
||||
| `exclude` | Do not evict pods owned by these kinds |
|
||||
|
||||
At most one of `include`/`exclude` may be set.
|
||||
|
||||
## Example
|
||||
|
||||
### Age-based eviction with state filter
|
||||
|
||||
```yaml
|
||||
apiVersion: descheduler/v1alpha2
|
||||
kind: DeschedulerPolicy
|
||||
@@ -145,11 +125,36 @@ profiles:
|
||||
- name: PodLifeTime
|
||||
args:
|
||||
maxPodLifeTimeSeconds: 86400 # 1 day
|
||||
states:
|
||||
- Running
|
||||
namespaces:
|
||||
include:
|
||||
- default
|
||||
states:
|
||||
- Running
|
||||
```
|
||||
|
||||
This configuration evicts Running pods in the `default` namespace that are older than 1 day.
|
||||
### Transition-based eviction for completed pods
|
||||
|
||||
```yaml
|
||||
apiVersion: descheduler/v1alpha2
|
||||
kind: DeschedulerPolicy
|
||||
profiles:
|
||||
- name: default
|
||||
plugins:
|
||||
deschedule:
|
||||
enabled:
|
||||
- name: PodLifeTime
|
||||
pluginConfig:
|
||||
- name: PodLifeTime
|
||||
args:
|
||||
states:
|
||||
- Succeeded
|
||||
conditions:
|
||||
- reason: PodCompleted
|
||||
status: "True"
|
||||
minTimeSinceLastTransitionSeconds: 14400
|
||||
namespaces:
|
||||
include:
|
||||
- default
|
||||
```
|
||||
|
||||
This configuration evicts Succeeded pods in the `default` namespace that have a `PodCompleted` condition with status `True` and whose last matching transition happened more than 4 hours ago.
|
||||
|
||||
@@ -26,17 +26,16 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
|
||||
|
||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||
frameworktypes "sigs.k8s.io/descheduler/pkg/framework/types"
|
||||
)
|
||||
|
||||
const PluginName = "PodLifeTime"
|
||||
|
||||
var _ frameworktypes.DeschedulePlugin = &PodLifeTime{}
|
||||
|
||||
// PodLifeTime evicts pods on the node that violate the max pod lifetime threshold
|
||||
// PodLifeTime evicts pods matching configurable lifetime and status transition criteria.
|
||||
type PodLifeTime struct {
|
||||
logger klog.Logger
|
||||
handle frameworktypes.Handle
|
||||
@@ -44,12 +43,13 @@ type PodLifeTime struct {
|
||||
podFilter podutil.FilterFunc
|
||||
}
|
||||
|
||||
// New builds plugin from its arguments while passing a handle
|
||||
// New builds plugin from its arguments while passing a handle.
|
||||
func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle) (frameworktypes.Plugin, error) {
|
||||
podLifeTimeArgs, ok := args.(*PodLifeTimeArgs)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("want args to be of type PodLifeTimeArgs, got %T", args)
|
||||
}
|
||||
|
||||
logger := klog.FromContext(ctx).WithValues("plugin", PluginName)
|
||||
|
||||
var includedNamespaces, excludedNamespaces sets.Set[string]
|
||||
@@ -69,53 +69,80 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
|
||||
return nil, fmt.Errorf("error initializing pod filter function: %v", err)
|
||||
}
|
||||
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
podAgeSeconds := int(metav1.Now().Sub(pod.GetCreationTimestamp().Local()).Seconds())
|
||||
return podAgeSeconds > int(*podLifeTimeArgs.MaxPodLifeTimeSeconds)
|
||||
})
|
||||
if podLifeTimeArgs.MaxPodLifeTimeSeconds != nil {
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
podAge := metav1.Now().Sub(pod.GetCreationTimestamp().Local())
|
||||
if podAge < 0 {
|
||||
return false
|
||||
}
|
||||
return uint(podAge.Seconds()) > *podLifeTimeArgs.MaxPodLifeTimeSeconds
|
||||
})
|
||||
}
|
||||
|
||||
if len(podLifeTimeArgs.States) > 0 {
|
||||
states := sets.New(podLifeTimeArgs.States...)
|
||||
includeInit := podLifeTimeArgs.IncludingInitContainers
|
||||
includeEphemeral := podLifeTimeArgs.IncludingEphemeralContainers
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
// Pod Status Phase
|
||||
if states.Has(string(pod.Status.Phase)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Pod Status Reason
|
||||
if states.Has(pod.Status.Reason) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Init Container Status Reason
|
||||
if podLifeTimeArgs.IncludingInitContainers {
|
||||
for _, containerStatus := range pod.Status.InitContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if podutil.HasMatchingContainerWaitingState(pod.Status.ContainerStatuses, states) ||
|
||||
podutil.HasMatchingContainerTerminatedState(pod.Status.ContainerStatuses, states) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ephemeral Container Status Reason
|
||||
if podLifeTimeArgs.IncludingEphemeralContainers {
|
||||
for _, containerStatus := range pod.Status.EphemeralContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if includeInit && (podutil.HasMatchingContainerWaitingState(pod.Status.InitContainerStatuses, states) ||
|
||||
podutil.HasMatchingContainerTerminatedState(pod.Status.InitContainerStatuses, states)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Container Status Reason
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
if includeEphemeral && (podutil.HasMatchingContainerWaitingState(pod.Status.EphemeralContainerStatuses, states) ||
|
||||
podutil.HasMatchingContainerTerminatedState(pod.Status.EphemeralContainerStatuses, states)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
if podLifeTimeArgs.OwnerKinds != nil {
|
||||
if len(podLifeTimeArgs.OwnerKinds.Include) > 0 {
|
||||
includeKinds := sets.New(podLifeTimeArgs.OwnerKinds.Include...)
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
for _, owner := range podutil.OwnerRef(pod) {
|
||||
if includeKinds.Has(owner.Kind) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
} else if len(podLifeTimeArgs.OwnerKinds.Exclude) > 0 {
|
||||
excludeKinds := sets.New(podLifeTimeArgs.OwnerKinds.Exclude...)
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
for _, owner := range podutil.OwnerRef(pod) {
|
||||
if excludeKinds.Has(owner.Kind) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(podLifeTimeArgs.Conditions) > 0 {
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
return matchesAnyPodConditionFilter(pod, podLifeTimeArgs.Conditions)
|
||||
})
|
||||
}
|
||||
|
||||
if len(podLifeTimeArgs.ExitCodes) > 0 {
|
||||
exitCodesSet := sets.New(podLifeTimeArgs.ExitCodes...)
|
||||
podFilter = podutil.WrapFilterFuncs(podFilter, func(pod *v1.Pod) bool {
|
||||
return matchesAnyExitCode(pod, exitCodesSet, podLifeTimeArgs.IncludingInitContainers)
|
||||
})
|
||||
}
|
||||
|
||||
return &PodLifeTime{
|
||||
logger: logger,
|
||||
handle: handle,
|
||||
@@ -132,7 +159,6 @@ func (d *PodLifeTime) Name() string {
|
||||
// Deschedule extension point implementation for the plugin
|
||||
func (d *PodLifeTime) Deschedule(ctx context.Context, nodes []*v1.Node) *frameworktypes.Status {
|
||||
podsToEvict := make([]*v1.Pod, 0)
|
||||
nodeMap := make(map[string]*v1.Node, len(nodes))
|
||||
logger := klog.FromContext(klog.NewContext(ctx, d.logger)).WithValues("ExtensionPoint", frameworktypes.DescheduleExtensionPoint)
|
||||
for _, node := range nodes {
|
||||
logger.V(2).Info("Processing node", "node", klog.KObj(node))
|
||||
@@ -143,8 +169,6 @@ func (d *PodLifeTime) Deschedule(ctx context.Context, nodes []*v1.Node) *framewo
|
||||
Err: fmt.Errorf("error listing pods on a node: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
nodeMap[node.Name] = node
|
||||
podsToEvict = append(podsToEvict, pods...)
|
||||
}
|
||||
|
||||
@@ -170,3 +194,61 @@ loop:
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchesAnyPodConditionFilter returns true if the pod has at least one
|
||||
// condition satisfying any of the given filters (OR across filters).
|
||||
func matchesAnyPodConditionFilter(pod *v1.Pod, filters []PodConditionFilter) bool {
|
||||
for _, f := range filters {
|
||||
for _, cond := range pod.Status.Conditions {
|
||||
if !matchesConditionFields(cond, f) {
|
||||
continue
|
||||
}
|
||||
if f.MinTimeSinceLastTransitionSeconds != nil {
|
||||
if cond.LastTransitionTime.IsZero() {
|
||||
continue
|
||||
}
|
||||
idle := metav1.Now().Sub(cond.LastTransitionTime.Time)
|
||||
if idle < 0 || uint(idle.Seconds()) < *f.MinTimeSinceLastTransitionSeconds {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchesConditionFields checks type, status, and reason fields of a single
|
||||
// condition against a filter. Unset filter fields are not checked.
|
||||
func matchesConditionFields(cond v1.PodCondition, filter PodConditionFilter) bool {
|
||||
if filter.Type != "" && string(cond.Type) != filter.Type {
|
||||
return false
|
||||
}
|
||||
if filter.Status != "" && string(cond.Status) != filter.Status {
|
||||
return false
|
||||
}
|
||||
if filter.Reason != "" && cond.Reason != filter.Reason {
|
||||
return false
|
||||
}
|
||||
// validation ensures that at least one of type, status, reason, or minTimeSinceLastTransitionSeconds is set
|
||||
return true
|
||||
}
|
||||
|
||||
func matchesAnyExitCode(pod *v1.Pod, exitCodes sets.Set[int32], includeInit bool) bool {
|
||||
if hasMatchingExitCode(pod.Status.ContainerStatuses, exitCodes) {
|
||||
return true
|
||||
}
|
||||
if includeInit && hasMatchingExitCode(pod.Status.InitContainerStatuses, exitCodes) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasMatchingExitCode(statuses []v1.ContainerStatus, exitCodes sets.Set[int32]) bool {
|
||||
for _, cs := range statuses {
|
||||
if cs.State.Terminated != nil && exitCodes.Has(cs.State.Terminated.ExitCode) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
|
||||
"sigs.k8s.io/descheduler/pkg/api"
|
||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor"
|
||||
frameworktesting "sigs.k8s.io/descheduler/pkg/framework/testing"
|
||||
@@ -40,6 +41,8 @@ const nodeName1 = "n1"
|
||||
var (
|
||||
olderPodCreationTime = metav1.NewTime(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
|
||||
newerPodCreationTime = metav1.NewTime(time.Now())
|
||||
oldTransitionTime = metav1.NewTime(time.Now().Add(-2 * time.Hour))
|
||||
newTransitionTime = metav1.NewTime(time.Now().Add(-1 * time.Minute))
|
||||
)
|
||||
|
||||
func buildTestNode1() *v1.Node {
|
||||
@@ -73,20 +76,32 @@ func buildTestPodWithRSOwnerRefWithPendingPhaseForNode1(name string, creationTim
|
||||
})
|
||||
}
|
||||
|
||||
func buildPod(name, nodeName string, apply func(*v1.Pod)) *v1.Pod {
|
||||
pod := test.BuildTestPod(name, 1, 1, nodeName, func(p *v1.Pod) {
|
||||
p.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
||||
if apply != nil {
|
||||
apply(p)
|
||||
}
|
||||
})
|
||||
return pod
|
||||
}
|
||||
|
||||
type podLifeTimeTestCase struct {
|
||||
description string
|
||||
args *PodLifeTimeArgs
|
||||
pods []*v1.Pod
|
||||
nodes []*v1.Node
|
||||
expectedEvictedPods []string // if specified, will assert specific pods were evicted
|
||||
expectedEvictedPods []string
|
||||
expectedEvictedPodCount uint
|
||||
ignorePvcPods bool
|
||||
nodeFit bool
|
||||
maxPodsToEvictPerNode *uint
|
||||
maxPodsToEvictPerNamespace *uint
|
||||
maxPodsToEvictTotal *uint
|
||||
}
|
||||
|
||||
func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -106,7 +121,7 @@ func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) {
|
||||
WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode).
|
||||
WithMaxPodsToEvictPerNamespace(tc.maxPodsToEvictPerNamespace).
|
||||
WithMaxPodsToEvictTotal(tc.maxPodsToEvictTotal),
|
||||
defaultevictor.DefaultEvictorArgs{IgnorePvcPods: tc.ignorePvcPods},
|
||||
defaultevictor.DefaultEvictorArgs{IgnorePvcPods: tc.ignorePvcPods, NodeFit: tc.nodeFit},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -804,3 +819,549 @@ func TestPodLifeTime_PodPhaseStates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for new fields (Conditions, ExitCodes, OwnerKinds, MinTimeSinceLastTransitionSeconds)
|
||||
// and extended States behavior (terminated reason matching)
|
||||
|
||||
func TestStatesWithTerminatedReasons(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with matching terminated reason via states",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"NodeAffinity"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "NodeAffinity"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "evict pod with matching pod status reason via states",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"Shutdown"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.Reason = "Shutdown"
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "states matches terminated reason on init container",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"CreateContainerConfigError"},
|
||||
IncludingInitContainers: true,
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.InitContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "CreateContainerConfigError"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "states does not match terminated reason on init container without flag",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"CreateContainerConfigError"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.InitContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "CreateContainerConfigError"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with matching condition reason",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True"},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
buildPod("p2", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "OtherReason", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
{
|
||||
description: "evict pod matching condition type only",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Type: string(v1.PodReady)},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "no matching conditions, 0 evictions",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted"},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodReady, Status: v1.ConditionTrue, Reason: "SomethingElse"},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransitionTimeFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
var fiveMinutes uint = 300
|
||||
var fourHours uint = 14400
|
||||
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with old transition time",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True", MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
{
|
||||
description: "do not evict pod with recent transition time",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{MinTimeSinceLastTransitionSeconds: &fourHours},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, LastTransitionTime: newTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
{
|
||||
description: "transition time scoped to matching conditions only",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
{Type: v1.PodReady, Reason: "OtherReason", LastTransitionTime: newTransitionTime},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
{
|
||||
description: "no conditions on pod, transition time filter returns false",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodRunning)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodRunning
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnerKindsFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "exclude Job owner kind: Job-owned pod not evicted",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
{
|
||||
description: "exclude Job owner kind: non-Job pod still evicted",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
buildPod("p2", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p2"},
|
||||
},
|
||||
{
|
||||
description: "include only Job owner kind",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &OwnerKinds{Include: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
buildPod("p2", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"p1"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatesWithEphemeralContainers(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "states matches terminated reason on ephemeral container",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"OOMKilled"},
|
||||
IncludingEphemeralContainers: true,
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.EphemeralContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "OOMKilled"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "states does not match ephemeral container without flag",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"OOMKilled"},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.EphemeralContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "OOMKilled"}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCodesFiltering(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "evict pod with matching exit code",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
ExitCodes: []int32{1},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{ExitCode: 1}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
},
|
||||
{
|
||||
description: "exit code not matched, 0 evictions",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
ExitCodes: []int32{2},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("p1", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{ExitCode: 1}}},
|
||||
}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 0,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedFilters(t *testing.T) {
|
||||
node := test.BuildTestNode("node1", 2000, 3000, 10, nil)
|
||||
var fiveMinutes uint = 300
|
||||
|
||||
testCases := []podLifeTimeTestCase{
|
||||
{
|
||||
description: "user scenario: Succeeded + PodCompleted condition + transition time threshold",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True", MinTimeSinceLastTransitionSeconds: &fiveMinutes},
|
||||
},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("stale-completed", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
{Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted", LastTransitionTime: oldTransitionTime},
|
||||
}
|
||||
}),
|
||||
buildPod("recent-completed", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodSucceeded
|
||||
p.Status.Conditions = []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted", LastTransitionTime: newTransitionTime},
|
||||
}
|
||||
}),
|
||||
buildPod("running-pod", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodRunning
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"stale-completed"},
|
||||
},
|
||||
{
|
||||
description: "failed pod removal compat: Failed + min age + exclude Job",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](0),
|
||||
OwnerKinds: &OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
nodes: []*v1.Node{node},
|
||||
pods: []*v1.Pod{
|
||||
buildPod("failed-rs", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{State: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "Error"}}},
|
||||
}
|
||||
}),
|
||||
buildPod("failed-job", "node1", func(p *v1.Pod) {
|
||||
p.Status.Phase = v1.PodFailed
|
||||
p.ObjectMeta.OwnerReferences = []metav1.OwnerReference{{Kind: "Job", Name: "job1", APIVersion: "batch/v1"}}
|
||||
}),
|
||||
},
|
||||
expectedEvictedPodCount: 1,
|
||||
expectedEvictedPods: []string{"failed-rs"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
runPodLifeTimeTest(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
args *PodLifeTimeArgs
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
description: "valid: states set",
|
||||
args: &PodLifeTimeArgs{States: []string{"Running"}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: conditions set",
|
||||
args: &PodLifeTimeArgs{Conditions: []PodConditionFilter{{Reason: "PodCompleted"}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: maxPodLifeTimeSeconds set",
|
||||
args: &PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](600)},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: states with maxPodLifeTimeSeconds",
|
||||
args: &PodLifeTimeArgs{States: []string{"Running"}, MaxPodLifeTimeSeconds: utilptr.To[uint](600)},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: states with conditions",
|
||||
args: &PodLifeTimeArgs{States: []string{"Succeeded"}, Conditions: []PodConditionFilter{{Reason: "PodCompleted"}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: exitCodes only",
|
||||
args: &PodLifeTimeArgs{ExitCodes: []int32{1}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: condition with minTimeSinceLastTransitionSeconds",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{{Reason: "PodCompleted", MinTimeSinceLastTransitionSeconds: utilptr.To[uint](300)}},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "valid: condition with only minTimeSinceLastTransitionSeconds",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{{MinTimeSinceLastTransitionSeconds: utilptr.To[uint](300)}},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "invalid: no filter criteria",
|
||||
args: &PodLifeTimeArgs{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: both include and exclude namespaces",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"Running"},
|
||||
Namespaces: &api.Namespaces{Include: []string{"a"}, Exclude: []string{"b"}},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: both include and exclude ownerKinds",
|
||||
args: &PodLifeTimeArgs{
|
||||
States: []string{"Running"},
|
||||
OwnerKinds: &OwnerKinds{Include: []string{"Job"}, Exclude: []string{"ReplicaSet"}},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: bad state name",
|
||||
args: &PodLifeTimeArgs{States: []string{"NotAState"}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
description: "invalid: empty condition filter",
|
||||
args: &PodLifeTimeArgs{
|
||||
Conditions: []PodConditionFilter{{}},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
err := ValidatePodLifeTimeArgs(tc.args)
|
||||
if tc.expectError && err == nil {
|
||||
t.Error("Expected validation error but got nil")
|
||||
}
|
||||
if !tc.expectError && err != nil {
|
||||
t.Errorf("Expected no validation error but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,50 @@ import (
|
||||
type PodLifeTimeArgs struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
Namespaces *api.Namespaces `json:"namespaces,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"`
|
||||
States []string `json:"states,omitempty"`
|
||||
IncludingInitContainers bool `json:"includingInitContainers,omitempty"`
|
||||
IncludingEphemeralContainers bool `json:"includingEphemeralContainers,omitempty"`
|
||||
Namespaces *api.Namespaces `json:"namespaces,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
OwnerKinds *OwnerKinds `json:"ownerKinds,omitempty"`
|
||||
|
||||
MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"`
|
||||
|
||||
// States filters pods by phase, pod status reason, container waiting reason,
|
||||
// or container terminated reason. A pod matches if any of its states appear
|
||||
// in this list.
|
||||
States []string `json:"states,omitempty"`
|
||||
|
||||
// Conditions filters pods by status.conditions entries. A pod matches if
|
||||
// any of its conditions satisfy at least one filter. Each filter can
|
||||
// optionally require a minimum time since the condition last transitioned.
|
||||
Conditions []PodConditionFilter `json:"conditions,omitempty"`
|
||||
|
||||
// ExitCodes filters by container terminated exit codes.
|
||||
ExitCodes []int32 `json:"exitCodes,omitempty"`
|
||||
|
||||
IncludingInitContainers bool `json:"includingInitContainers,omitempty"`
|
||||
IncludingEphemeralContainers bool `json:"includingEphemeralContainers,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// OwnerKinds allows filtering pods by owner reference kinds with include/exclude support.
|
||||
// At most one of Include/Exclude may be set.
|
||||
type OwnerKinds struct {
|
||||
Include []string `json:"include,omitempty"`
|
||||
Exclude []string `json:"exclude,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// PodConditionFilter matches a pod condition by type, status, and/or reason.
|
||||
// All specified fields must match (AND). Unset fields are not checked.
|
||||
// When MinTimeSinceLastTransitionSeconds is set, the condition must also have
|
||||
// a lastTransitionTime older than this many seconds.
|
||||
type PodConditionFilter struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
|
||||
// MinTimeSinceLastTransitionSeconds requires the matching condition's
|
||||
// lastTransitionTime to be at least this many seconds in the past.
|
||||
MinTimeSinceLastTransitionSeconds *uint `json:"minTimeSinceLastTransitionSeconds,omitempty"`
|
||||
}
|
||||
|
||||
@@ -27,57 +27,78 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
var podLifeTimeAllowedStates = sets.New(
|
||||
// Pod Status Phase
|
||||
string(v1.PodRunning),
|
||||
string(v1.PodPending),
|
||||
string(v1.PodSucceeded),
|
||||
string(v1.PodFailed),
|
||||
string(v1.PodUnknown),
|
||||
|
||||
// Pod Status Reasons
|
||||
"NodeAffinity",
|
||||
"NodeLost",
|
||||
"Shutdown",
|
||||
"UnexpectedAdmissionError",
|
||||
|
||||
// Container State Waiting Reasons
|
||||
"PodInitializing",
|
||||
"ContainerCreating",
|
||||
"ImagePullBackOff",
|
||||
"CrashLoopBackOff",
|
||||
"CreateContainerConfigError",
|
||||
"ErrImagePull",
|
||||
"CreateContainerError",
|
||||
"InvalidImageName",
|
||||
|
||||
// Container State Terminated Reasons
|
||||
"OOMKilled",
|
||||
"Error",
|
||||
"Completed",
|
||||
"DeadlineExceeded",
|
||||
"Evicted",
|
||||
"ContainerCannotRun",
|
||||
"StartError",
|
||||
)
|
||||
|
||||
// ValidatePodLifeTimeArgs validates PodLifeTime arguments
|
||||
func ValidatePodLifeTimeArgs(obj runtime.Object) error {
|
||||
args := obj.(*PodLifeTimeArgs)
|
||||
var allErrs []error
|
||||
if args.MaxPodLifeTimeSeconds == nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("MaxPodLifeTimeSeconds not set"))
|
||||
}
|
||||
|
||||
// At most one of include/exclude can be set
|
||||
if args.Namespaces != nil && len(args.Namespaces.Include) > 0 && len(args.Namespaces.Exclude) > 0 {
|
||||
allErrs = append(allErrs, fmt.Errorf("only one of Include/Exclude namespaces can be set"))
|
||||
}
|
||||
|
||||
if args.OwnerKinds != nil && len(args.OwnerKinds.Include) > 0 && len(args.OwnerKinds.Exclude) > 0 {
|
||||
allErrs = append(allErrs, fmt.Errorf("only one of Include/Exclude ownerKinds can be set"))
|
||||
}
|
||||
|
||||
if args.LabelSelector != nil {
|
||||
if _, err := metav1.LabelSelectorAsSelector(args.LabelSelector); err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("failed to get label selectors from strategy's params: %+v", err))
|
||||
}
|
||||
}
|
||||
podLifeTimeAllowedStates := sets.New(
|
||||
// Pod Status Phase
|
||||
string(v1.PodRunning),
|
||||
string(v1.PodPending),
|
||||
string(v1.PodSucceeded),
|
||||
string(v1.PodFailed),
|
||||
string(v1.PodUnknown),
|
||||
|
||||
// Pod Status Reasons
|
||||
"NodeAffinity",
|
||||
"NodeLost",
|
||||
"Shutdown",
|
||||
"UnexpectedAdmissionError",
|
||||
|
||||
// Container Status Reasons
|
||||
// Container state reasons: https://github.com/kubernetes/kubernetes/blob/release-1.24/pkg/kubelet/kubelet_pods.go#L76-L79
|
||||
"PodInitializing",
|
||||
"ContainerCreating",
|
||||
|
||||
// containerStatuses[*].state.waiting.reason: ImagePullBackOff, etc.
|
||||
"ImagePullBackOff",
|
||||
"CrashLoopBackOff",
|
||||
"CreateContainerConfigError",
|
||||
"ErrImagePull",
|
||||
"CreateContainerError",
|
||||
"InvalidImageName",
|
||||
)
|
||||
|
||||
if !podLifeTimeAllowedStates.HasAll(args.States...) {
|
||||
if len(args.States) > 0 && !podLifeTimeAllowedStates.HasAll(args.States...) {
|
||||
allowed := podLifeTimeAllowedStates.UnsortedList()
|
||||
sort.Strings(allowed)
|
||||
allErrs = append(allErrs, fmt.Errorf("states must be one of %v", allowed))
|
||||
}
|
||||
|
||||
for i, c := range args.Conditions {
|
||||
if c.Type == "" && c.Status == "" && c.Reason == "" && c.MinTimeSinceLastTransitionSeconds == nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("conditions[%d]: at least one of type, status, reason, or minTimeSinceLastTransitionSeconds must be set", i))
|
||||
}
|
||||
}
|
||||
|
||||
hasFilter := args.MaxPodLifeTimeSeconds != nil ||
|
||||
len(args.States) > 0 ||
|
||||
len(args.Conditions) > 0 ||
|
||||
len(args.ExitCodes) > 0
|
||||
if !hasFilter {
|
||||
allErrs = append(allErrs, fmt.Errorf("at least one filtering criterion must be specified (maxPodLifeTimeSeconds, states, conditions, or exitCodes)"))
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) {
|
||||
args: &PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: nil,
|
||||
},
|
||||
errInfo: fmt.Errorf("MaxPodLifeTimeSeconds not set"),
|
||||
errInfo: fmt.Errorf("at least one filtering criterion must be specified (maxPodLifeTimeSeconds, states, conditions, or exitCodes)"),
|
||||
},
|
||||
{
|
||||
description: "invalid pod state arg, expects errors",
|
||||
@@ -63,7 +63,7 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) {
|
||||
MaxPodLifeTimeSeconds: func(i uint) *uint { return &i }(1),
|
||||
States: []string{string("InvalidState")},
|
||||
},
|
||||
errInfo: fmt.Errorf("states must be one of [ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError ErrImagePull Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost Pending PodInitializing Running Shutdown Succeeded UnexpectedAdmissionError Unknown]"),
|
||||
errInfo: fmt.Errorf("states must be one of [Completed ContainerCannotRun ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError DeadlineExceeded ErrImagePull Error Evicted Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost OOMKilled Pending PodInitializing Running Shutdown StartError Succeeded UnexpectedAdmissionError Unknown]"),
|
||||
},
|
||||
{
|
||||
description: "nil MaxPodLifeTimeSeconds arg and invalid pod state arg, expects errors",
|
||||
@@ -71,7 +71,7 @@ func TestValidateRemovePodLifeTimeArgs(t *testing.T) {
|
||||
MaxPodLifeTimeSeconds: nil,
|
||||
States: []string{string("InvalidState")},
|
||||
},
|
||||
errInfo: fmt.Errorf("[MaxPodLifeTimeSeconds not set, states must be one of [ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError ErrImagePull Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost Pending PodInitializing Running Shutdown Succeeded UnexpectedAdmissionError Unknown]]"),
|
||||
errInfo: fmt.Errorf("states must be one of [Completed ContainerCannotRun ContainerCreating CrashLoopBackOff CreateContainerConfigError CreateContainerError DeadlineExceeded ErrImagePull Error Evicted Failed ImagePullBackOff InvalidImageName NodeAffinity NodeLost OOMKilled Pending PodInitializing Running Shutdown StartError Succeeded UnexpectedAdmissionError Unknown]"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,53 @@ import (
|
||||
api "sigs.k8s.io/descheduler/pkg/api"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerKinds) DeepCopyInto(out *OwnerKinds) {
|
||||
*out = *in
|
||||
if in.Include != nil {
|
||||
in, out := &in.Include, &out.Include
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Exclude != nil {
|
||||
in, out := &in.Exclude, &out.Exclude
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerKinds.
|
||||
func (in *OwnerKinds) DeepCopy() *OwnerKinds {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerKinds)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodConditionFilter) DeepCopyInto(out *PodConditionFilter) {
|
||||
*out = *in
|
||||
if in.MinTimeSinceLastTransitionSeconds != nil {
|
||||
in, out := &in.MinTimeSinceLastTransitionSeconds, &out.MinTimeSinceLastTransitionSeconds
|
||||
*out = new(uint)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodConditionFilter.
|
||||
func (in *PodConditionFilter) DeepCopy() *PodConditionFilter {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodConditionFilter)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) {
|
||||
*out = *in
|
||||
@@ -41,6 +88,11 @@ func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) {
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.OwnerKinds != nil {
|
||||
in, out := &in.OwnerKinds, &out.OwnerKinds
|
||||
*out = new(OwnerKinds)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.MaxPodLifeTimeSeconds != nil {
|
||||
in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds
|
||||
*out = new(uint)
|
||||
@@ -51,6 +103,18 @@ func (in *PodLifeTimeArgs) DeepCopyInto(out *PodLifeTimeArgs) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]PodConditionFilter, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ExitCodes != nil {
|
||||
in, out := &in.ExitCodes, &out.ExitCodes
|
||||
*out = make([]int32, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -84,19 +84,13 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
|
||||
if states.Has(string(pod.Status.Phase)) {
|
||||
return true
|
||||
}
|
||||
|
||||
containerStatuses := pod.Status.ContainerStatuses
|
||||
|
||||
if tooManyRestartsArgs.IncludingInitContainers {
|
||||
containerStatuses = append(containerStatuses, pod.Status.InitContainerStatuses...)
|
||||
if podutil.HasMatchingContainerWaitingState(pod.Status.ContainerStatuses, states) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, containerStatus := range containerStatuses {
|
||||
if containerStatus.State.Waiting != nil && states.Has(containerStatus.State.Waiting.Reason) {
|
||||
return true
|
||||
}
|
||||
if tooManyRestartsArgs.IncludingInitContainers &&
|
||||
podutil.HasMatchingContainerWaitingState(pod.Status.InitContainerStatuses, states) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
sdkresource "go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
|
||||
deschedulerapi "sigs.k8s.io/descheduler/pkg/api"
|
||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/defaultevictor"
|
||||
"sigs.k8s.io/descheduler/pkg/framework/plugins/podlifetime"
|
||||
)
|
||||
|
||||
func TestPodLifeTime_FailedPods(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clientSet, _, nodeLister, _ := initializeClient(ctx, t)
|
||||
|
||||
t.Log("Creating testing namespace")
|
||||
testNamespace := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "e2e-" + strings.ReplaceAll(strings.ToLower(t.Name()), "_", "-")}}
|
||||
if _, err := clientSet.CoreV1().Namespaces().Create(ctx, testNamespace, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Unable to create ns %v", testNamespace.Name)
|
||||
}
|
||||
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedEvictedPodCount int
|
||||
args *podlifetime.PodLifeTimeArgs
|
||||
}{
|
||||
{
|
||||
name: "test-transition-failed-pods-default",
|
||||
expectedEvictedPodCount: 1,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test-transition-failed-pods-exclude-job",
|
||||
expectedEvictedPodCount: 0,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodFailed)},
|
||||
OwnerKinds: &podlifetime.OwnerKinds{Exclude: []string{"Job"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
job := initTransitionTestJob(tc.name, testNamespace.Name)
|
||||
t.Logf("Creating job %s in %s namespace", job.Name, testNamespace.Name)
|
||||
jobClient := clientSet.BatchV1().Jobs(testNamespace.Name)
|
||||
if _, err := jobClient.Create(ctx, job, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Error creating Job %s: %v", tc.name, err)
|
||||
}
|
||||
deletePropagationPolicy := metav1.DeletePropagationForeground
|
||||
defer func() {
|
||||
jobClient.Delete(ctx, job.Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy})
|
||||
waitForPodsToDisappear(ctx, t, clientSet, job.Labels, testNamespace.Name)
|
||||
}()
|
||||
waitForTransitionJobPodPhase(ctx, t, clientSet, job, v1.PodFailed)
|
||||
|
||||
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
|
||||
tc.args.Namespaces = &deschedulerapi.Namespaces{
|
||||
Include: []string{testNamespace.Name},
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, tc.args,
|
||||
defaultevictor.DefaultEvictorArgs{EvictLocalStoragePods: true},
|
||||
nil,
|
||||
)
|
||||
|
||||
var meetsExpectations bool
|
||||
var actualEvictedPodCount int
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
currentRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
actualEvictedPod := preRunNames.Difference(currentRunNames)
|
||||
actualEvictedPodCount = actualEvictedPod.Len()
|
||||
t.Logf("preRunNames: %v, currentRunNames: %v, actualEvictedPodCount: %v\n", preRunNames.List(), currentRunNames.List(), actualEvictedPodCount)
|
||||
if actualEvictedPodCount != tc.expectedEvictedPodCount {
|
||||
t.Logf("Expecting %v number of pods evicted, got %v instead", tc.expectedEvictedPodCount, actualEvictedPodCount)
|
||||
return false, nil
|
||||
}
|
||||
meetsExpectations = true
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
t.Errorf("Error waiting for expected eviction count: %v", err)
|
||||
}
|
||||
|
||||
if !meetsExpectations {
|
||||
t.Errorf("Unexpected number of pods have been evicted, got %v, expected %v", actualEvictedPodCount, tc.expectedEvictedPodCount)
|
||||
} else {
|
||||
t.Logf("Total of %d Pods were evicted for %s", actualEvictedPodCount, tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodLifeTime_SucceededPods(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clientSet, _, nodeLister, _ := initializeClient(ctx, t)
|
||||
|
||||
t.Log("Creating testing namespace")
|
||||
testNamespace := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "e2e-" + strings.ReplaceAll(strings.ToLower(t.Name()), "_", "-")}}
|
||||
if _, err := clientSet.CoreV1().Namespaces().Create(ctx, testNamespace, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Unable to create ns %v", testNamespace.Name)
|
||||
}
|
||||
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedEvictedPodCount int
|
||||
args *podlifetime.PodLifeTimeArgs
|
||||
}{
|
||||
{
|
||||
name: "test-transition-succeeded-pods",
|
||||
expectedEvictedPodCount: 1,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test-transition-succeeded-condition",
|
||||
expectedEvictedPodCount: 1,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []podlifetime.PodConditionFilter{
|
||||
{Reason: "PodCompleted", Status: "True"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test-transition-succeeded-condition-unmatched",
|
||||
expectedEvictedPodCount: 0,
|
||||
args: &podlifetime.PodLifeTimeArgs{
|
||||
States: []string{string(v1.PodSucceeded)},
|
||||
Conditions: []podlifetime.PodConditionFilter{
|
||||
{Reason: "ReasonDoesNotMatch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
job := initTransitionSucceededJob(tc.name, testNamespace.Name)
|
||||
t.Logf("Creating job %s in %s namespace", job.Name, testNamespace.Name)
|
||||
jobClient := clientSet.BatchV1().Jobs(testNamespace.Name)
|
||||
if _, err := jobClient.Create(ctx, job, metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("Error creating Job %s: %v", tc.name, err)
|
||||
}
|
||||
deletePropagationPolicy := metav1.DeletePropagationForeground
|
||||
defer func() {
|
||||
jobClient.Delete(ctx, job.Name, metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy})
|
||||
waitForPodsToDisappear(ctx, t, clientSet, job.Labels, testNamespace.Name)
|
||||
}()
|
||||
waitForTransitionJobPodPhase(ctx, t, clientSet, job, v1.PodSucceeded)
|
||||
|
||||
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
|
||||
tc.args.Namespaces = &deschedulerapi.Namespaces{
|
||||
Include: []string{testNamespace.Name},
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, tc.args,
|
||||
defaultevictor.DefaultEvictorArgs{EvictLocalStoragePods: true},
|
||||
nil,
|
||||
)
|
||||
|
||||
var meetsExpectations bool
|
||||
var actualEvictedPodCount int
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
currentRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
|
||||
actualEvictedPod := preRunNames.Difference(currentRunNames)
|
||||
actualEvictedPodCount = actualEvictedPod.Len()
|
||||
t.Logf("preRunNames: %v, currentRunNames: %v, actualEvictedPodCount: %v\n", preRunNames.List(), currentRunNames.List(), actualEvictedPodCount)
|
||||
if actualEvictedPodCount != tc.expectedEvictedPodCount {
|
||||
t.Logf("Expecting %v number of pods evicted, got %v instead", tc.expectedEvictedPodCount, actualEvictedPodCount)
|
||||
return false, nil
|
||||
}
|
||||
meetsExpectations = true
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
t.Errorf("Error waiting for expected eviction count: %v", err)
|
||||
}
|
||||
|
||||
if !meetsExpectations {
|
||||
t.Errorf("Unexpected number of pods have been evicted, got %v, expected %v", actualEvictedPodCount, tc.expectedEvictedPodCount)
|
||||
} else {
|
||||
t.Logf("Total of %d Pods were evicted for %s", actualEvictedPodCount, tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func initTransitionTestJob(name, namespace string) *batchv1.Job {
|
||||
podSpec := makePodSpec("", nil)
|
||||
podSpec.Containers[0].Command = []string{"/bin/false"}
|
||||
podSpec.RestartPolicy = v1.RestartPolicyNever
|
||||
labelsSet := labels.Set{"test": name, "name": name}
|
||||
return &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labelsSet,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: podSpec,
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: labelsSet},
|
||||
},
|
||||
BackoffLimit: utilptr.To[int32](0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func initTransitionSucceededJob(name, namespace string) *batchv1.Job {
|
||||
podSpec := makePodSpec("", nil)
|
||||
podSpec.Containers[0].Image = "registry.k8s.io/e2e-test-images/agnhost:2.43"
|
||||
podSpec.Containers[0].Command = []string{"/bin/sh", "-c", "exit 0"}
|
||||
podSpec.RestartPolicy = v1.RestartPolicyNever
|
||||
labelsSet := labels.Set{"test": name, "name": name}
|
||||
return &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labelsSet,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: podSpec,
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: labelsSet},
|
||||
},
|
||||
BackoffLimit: utilptr.To[int32](0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func waitForTransitionJobPodPhase(ctx context.Context, t *testing.T, clientSet clientset.Interface, job *batchv1.Job, phase v1.PodPhase) {
|
||||
podClient := clientSet.CoreV1().Pods(job.Namespace)
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
t.Log(labels.FormatLabels(job.Labels))
|
||||
if podList, err := podClient.List(ctx, metav1.ListOptions{LabelSelector: labels.FormatLabels(job.Labels)}); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
if len(podList.Items) == 0 {
|
||||
t.Logf("Job controller has not created Pod for job %s yet", job.Name)
|
||||
return false, nil
|
||||
}
|
||||
for _, pod := range podList.Items {
|
||||
if pod.Status.Phase != phase {
|
||||
t.Logf("Pod %v not in %s phase yet, is %v instead", pod.Name, phase, pod.Status.Phase)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
t.Logf("Job %v Pod is in %s phase now", job.Name, phase)
|
||||
return true, nil
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatalf("Error waiting for pods in %s phase: %v", phase, err)
|
||||
}
|
||||
}
|
||||
+78
-50
@@ -421,14 +421,12 @@ func runPodLifetimePlugin(
|
||||
t *testing.T,
|
||||
clientset clientset.Interface,
|
||||
nodeLister listersv1.NodeLister,
|
||||
namespaces *deschedulerapi.Namespaces,
|
||||
priorityClass string,
|
||||
priority *int32,
|
||||
evictCritical bool,
|
||||
evictDaemonSet bool,
|
||||
maxPodsToEvictPerNamespace *uint,
|
||||
labelSelector *metav1.LabelSelector,
|
||||
args *podlifetime.PodLifeTimeArgs,
|
||||
evictorArgs defaultevictor.DefaultEvictorArgs,
|
||||
evictionOpts *evictions.Options,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
evictionPolicyGroupVersion, err := eutils.SupportEviction(clientset)
|
||||
if err != nil || len(evictionPolicyGroupVersion) == 0 {
|
||||
t.Fatalf("%v", err)
|
||||
@@ -439,42 +437,18 @@ func runPodLifetimePlugin(
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
var thresholdPriority int32
|
||||
if priority != nil {
|
||||
thresholdPriority = *priority
|
||||
if evictionOpts == nil {
|
||||
evictionOpts = evictions.NewOptions().WithPolicyGroupVersion(evictionPolicyGroupVersion)
|
||||
} else {
|
||||
thresholdPriority, err = utils.GetPriorityFromPriorityClass(ctx, clientset, priorityClass)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get threshold priority from plugin arg params")
|
||||
}
|
||||
evictionOpts = evictionOpts.WithPolicyGroupVersion(evictionPolicyGroupVersion)
|
||||
}
|
||||
|
||||
handle, _, err := frameworktesting.InitFrameworkHandle(
|
||||
ctx,
|
||||
clientset,
|
||||
evictions.NewOptions().
|
||||
WithPolicyGroupVersion(evictionPolicyGroupVersion).
|
||||
WithMaxPodsToEvictPerNamespace(maxPodsToEvictPerNamespace),
|
||||
defaultevictor.DefaultEvictorArgs{
|
||||
EvictSystemCriticalPods: evictCritical,
|
||||
EvictDaemonSetPods: evictDaemonSet,
|
||||
PriorityThreshold: &deschedulerapi.PriorityThreshold{
|
||||
Value: &thresholdPriority,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
handle, _, err := frameworktesting.InitFrameworkHandle(ctx, clientset, evictionOpts, evictorArgs, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize a framework handle: %v", err)
|
||||
}
|
||||
|
||||
maxPodLifeTimeSeconds := uint(1)
|
||||
|
||||
plugin, err := podlifetime.New(ctx, &podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: &maxPodLifeTimeSeconds,
|
||||
LabelSelector: labelSelector,
|
||||
Namespaces: namespaces,
|
||||
}, handle)
|
||||
plugin, err := podlifetime.New(ctx, args, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize the plugin: %v", err)
|
||||
}
|
||||
@@ -707,9 +681,14 @@ func TestNamespaceConstraintsInclude(t *testing.T) {
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
t.Logf("run the plugin to delete pods from %v namespace", rc.Namespace)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, &deschedulerapi.Namespaces{
|
||||
Include: []string{rc.Namespace},
|
||||
}, "", nil, false, false, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1),
|
||||
Namespaces: &deschedulerapi.Namespaces{Include: []string{rc.Namespace}},
|
||||
},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
// All pods are supposed to be deleted, wait until all the old pods are deleted
|
||||
if err := wait.PollUntilContextTimeout(ctx, time.Second, 20*time.Second, true, func(ctx context.Context) (bool, error) {
|
||||
@@ -777,9 +756,14 @@ func TestNamespaceConstraintsExclude(t *testing.T) {
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
t.Logf("run the plugin to delete pods from namespaces except the %v namespace", rc.Namespace)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, &deschedulerapi.Namespaces{
|
||||
Exclude: []string{rc.Namespace},
|
||||
}, "", nil, false, false, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1),
|
||||
Namespaces: &deschedulerapi.Namespaces{Exclude: []string{rc.Namespace}},
|
||||
},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
t.Logf("Waiting 10s")
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -890,11 +874,24 @@ func testEvictSystemCritical(t *testing.T, isPriorityClass bool) {
|
||||
sort.Strings(initialPodNames)
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
var thresholdPriority int32
|
||||
if isPriorityClass {
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, highPriorityClass.Name, nil, true, false, nil, nil)
|
||||
resolved, err := utils.GetPriorityFromPriorityClass(ctx, clientSet, highPriorityClass.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get priority from priority class: %v", err)
|
||||
}
|
||||
thresholdPriority = resolved
|
||||
} else {
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", &highPriority, true, false, nil, nil)
|
||||
thresholdPriority = highPriority
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{
|
||||
EvictSystemCriticalPods: true,
|
||||
PriorityThreshold: &deschedulerapi.PriorityThreshold{Value: &thresholdPriority},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// All pods are supposed to be deleted, wait until all pods in the test namespace are terminating
|
||||
t.Logf("All pods in the test namespace, no matter their priority (including system-node-critical and system-cluster-critical), will be deleted")
|
||||
@@ -961,7 +958,11 @@ func testEvictDaemonSetPod(t *testing.T, isDaemonSet bool) {
|
||||
sort.Strings(initialPodNames)
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", nil, false, isDaemonSet, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{EvictDaemonSetPods: isDaemonSet},
|
||||
nil,
|
||||
)
|
||||
|
||||
// All pods are supposed to be deleted, wait until all pods in the test namespace are terminating
|
||||
t.Logf("All daemonset pods in the test namespace, will be deleted")
|
||||
@@ -1074,13 +1075,25 @@ func testPriority(t *testing.T, isPriorityClass bool) {
|
||||
sort.Strings(expectEvictPodNames)
|
||||
t.Logf("Pods not expected to be evicted: %v, pods expected to be evicted: %v", expectReservePodNames, expectEvictPodNames)
|
||||
|
||||
var thresholdPriority int32
|
||||
if isPriorityClass {
|
||||
t.Logf("run the plugin to delete pods with priority lower than priority class %s", highPriorityClass.Name)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, highPriorityClass.Name, nil, false, false, nil, nil)
|
||||
resolved, err := utils.GetPriorityFromPriorityClass(ctx, clientSet, highPriorityClass.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get priority from priority class: %v", err)
|
||||
}
|
||||
thresholdPriority = resolved
|
||||
} else {
|
||||
t.Logf("run the plugin to delete pods with priority lower than %d", highPriority)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", &highPriority, false, false, nil, nil)
|
||||
thresholdPriority = highPriority
|
||||
}
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{
|
||||
PriorityThreshold: &deschedulerapi.PriorityThreshold{Value: &thresholdPriority},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
t.Logf("Waiting 10s")
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -1182,7 +1195,14 @@ func TestPodLabelSelector(t *testing.T) {
|
||||
t.Logf("Pods not expected to be evicted: %v, pods expected to be evicted: %v", expectReservePodNames, expectEvictPodNames)
|
||||
|
||||
t.Logf("run the plugin to delete pods with label test:podlifetime-evict")
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer, nil, "", nil, false, false, nil, &metav1.LabelSelector{MatchLabels: map[string]string{"test": "podlifetime-evict"}})
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeInformer,
|
||||
&podlifetime.PodLifeTimeArgs{
|
||||
MaxPodLifeTimeSeconds: utilptr.To[uint](1),
|
||||
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": "podlifetime-evict"}},
|
||||
},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
t.Logf("Waiting 10s")
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -1281,7 +1301,11 @@ func TestEvictAnnotation(t *testing.T) {
|
||||
t.Logf("Existing pods: %v", initialPodNames)
|
||||
|
||||
t.Log("Running PodLifetime plugin")
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, nil, "", nil, false, false, nil, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
nil,
|
||||
)
|
||||
|
||||
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, time.Minute, true, func(ctx context.Context) (bool, error) {
|
||||
podList, err = clientSet.CoreV1().Pods(rc.Namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(rc.Spec.Template.Labels).String()})
|
||||
@@ -1346,7 +1370,11 @@ func TestPodLifeTimeOldestEvicted(t *testing.T) {
|
||||
|
||||
t.Log("Running PodLifetime plugin with maxPodsToEvictPerNamespace=1 to ensure only the oldest pod is evicted")
|
||||
var maxPodsToEvictPerNamespace uint = 1
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister, nil, "", nil, false, false, &maxPodsToEvictPerNamespace, nil)
|
||||
runPodLifetimePlugin(ctx, t, clientSet, nodeLister,
|
||||
&podlifetime.PodLifeTimeArgs{MaxPodLifeTimeSeconds: utilptr.To[uint](1)},
|
||||
defaultevictor.DefaultEvictorArgs{},
|
||||
evictions.NewOptions().WithMaxPodsToEvictPerNamespace(&maxPodsToEvictPerNamespace),
|
||||
)
|
||||
t.Log("Finished PodLifetime plugin")
|
||||
|
||||
t.Logf("Wait for terminating pod to disappear")
|
||||
|
||||
Vendored
-1
@@ -16,7 +16,6 @@ go_library(
|
||||
importpath = "cel.dev/expr",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@org_golang_google_genproto_googleapis_rpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//reflect/protoreflect",
|
||||
"@org_golang_google_protobuf//runtime/protoimpl",
|
||||
"@org_golang_google_protobuf//types/known/anypb",
|
||||
|
||||
Vendored
+2
-20
@@ -11,26 +11,9 @@ bazel_dep(
|
||||
version = "0.39.1",
|
||||
repo_name = "bazel_gazelle",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis",
|
||||
version = "0.0.0-20241220-5e258e33.bcr.1",
|
||||
repo_name = "com_google_googleapis",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis-cc",
|
||||
version = "1.0.0",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis-java",
|
||||
version = "1.0.0",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "googleapis-go",
|
||||
version = "1.0.0",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "protobuf",
|
||||
version = "27.0",
|
||||
version = "27.1",
|
||||
repo_name = "com_google_protobuf",
|
||||
)
|
||||
bazel_dep(
|
||||
@@ -63,12 +46,11 @@ python.toolchain(
|
||||
)
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.22.0")
|
||||
go_sdk.download(version = "1.23.0")
|
||||
|
||||
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
|
||||
go_deps.from_file(go_mod = "//:go.mod")
|
||||
use_repo(
|
||||
go_deps,
|
||||
"org_golang_google_genproto_googleapis_rpc",
|
||||
"org_golang_google_protobuf",
|
||||
)
|
||||
|
||||
+274
-475
File diff suppressed because it is too large
Load Diff
+29
-48
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/eval.proto
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -373,58 +374,39 @@ func (x *EvalState_Result) GetValue() int64 {
|
||||
|
||||
var File_cel_expr_eval_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_cel_expr_eval_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x65, 0x76, 0x61, 0x6c, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x1a,
|
||||
0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x63, 0x65, 0x6c, 0x2f,
|
||||
0x65, 0x78, 0x70, 0x72, 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x22, 0xa2, 0x01, 0x0a, 0x09, 0x45, 0x76, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2b,
|
||||
0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x72,
|
||||
0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63,
|
||||
0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x73, 0x1a, 0x32, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x65,
|
||||
0x78, 0x70, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x65, 0x78, 0x70, 0x72, 0x12,
|
||||
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x09, 0x45, 0x78, 0x70, 0x72, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x05,
|
||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x65,
|
||||
0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x48,
|
||||
0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x07, 0x75, 0x6e, 0x6b, 0x6e,
|
||||
0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x65, 0x6c, 0x2e,
|
||||
0x65, 0x78, 0x70, 0x72, 0x2e, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x65, 0x74, 0x48,
|
||||
0x00, 0x52, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69,
|
||||
0x6e, 0x64, 0x22, 0x34, 0x0a, 0x08, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x12, 0x28,
|
||||
0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x66, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 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, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
|
||||
0x22, 0x22, 0x0a, 0x0a, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x65, 0x74, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x65, 0x78, 0x70, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x05, 0x65,
|
||||
0x78, 0x70, 0x72, 0x73, 0x42, 0x2c, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x2e, 0x63, 0x65, 0x6c, 0x2e,
|
||||
0x65, 0x78, 0x70, 0x72, 0x42, 0x09, 0x45, 0x76, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50,
|
||||
0x01, 0x5a, 0x0c, 0x63, 0x65, 0x6c, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x70, 0x72, 0xf8,
|
||||
0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
const file_cel_expr_eval_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13cel/expr/eval.proto\x12\bcel.expr\x1a\x19google/protobuf/any.proto\x1a\x14cel/expr/value.proto\"\xa2\x01\n" +
|
||||
"\tEvalState\x12+\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x13.cel.expr.ExprValueR\x06values\x124\n" +
|
||||
"\aresults\x18\x03 \x03(\v2\x1a.cel.expr.EvalState.ResultR\aresults\x1a2\n" +
|
||||
"\x06Result\x12\x12\n" +
|
||||
"\x04expr\x18\x01 \x01(\x03R\x04expr\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x03R\x05value\"\x9a\x01\n" +
|
||||
"\tExprValue\x12'\n" +
|
||||
"\x05value\x18\x01 \x01(\v2\x0f.cel.expr.ValueH\x00R\x05value\x12*\n" +
|
||||
"\x05error\x18\x02 \x01(\v2\x12.cel.expr.ErrorSetH\x00R\x05error\x120\n" +
|
||||
"\aunknown\x18\x03 \x01(\v2\x14.cel.expr.UnknownSetH\x00R\aunknownB\x06\n" +
|
||||
"\x04kind\"4\n" +
|
||||
"\bErrorSet\x12(\n" +
|
||||
"\x06errors\x18\x01 \x03(\v2\x10.cel.expr.StatusR\x06errors\"f\n" +
|
||||
"\x06Status\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n" +
|
||||
"\amessage\x18\x02 \x01(\tR\amessage\x12.\n" +
|
||||
"\adetails\x18\x03 \x03(\v2\x14.google.protobuf.AnyR\adetails\"\"\n" +
|
||||
"\n" +
|
||||
"UnknownSet\x12\x14\n" +
|
||||
"\x05exprs\x18\x01 \x03(\x03R\x05exprsB,\n" +
|
||||
"\fdev.cel.exprB\tEvalProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_eval_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_eval_proto_rawDescData = file_cel_expr_eval_proto_rawDesc
|
||||
file_cel_expr_eval_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_eval_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_eval_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_eval_proto_rawDescData = protoimpl.X.CompressGZIP(file_cel_expr_eval_proto_rawDescData)
|
||||
file_cel_expr_eval_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_eval_proto_rawDescData
|
||||
}
|
||||
@@ -470,7 +452,7 @@ func file_cel_expr_eval_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_cel_expr_eval_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
@@ -481,7 +463,6 @@ func file_cel_expr_eval_proto_init() {
|
||||
MessageInfos: file_cel_expr_eval_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_eval_proto = out.File
|
||||
file_cel_expr_eval_proto_rawDesc = nil
|
||||
file_cel_expr_eval_proto_goTypes = nil
|
||||
file_cel_expr_eval_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+36
-77
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.5
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/explain.proto
|
||||
|
||||
package expr
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,23 +21,20 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Deprecated: Do not use.
|
||||
// Deprecated: Marked as deprecated in cel/expr/explain.proto.
|
||||
type Explain struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
ExprSteps []*Explain_ExprStep `protobuf:"bytes,2,rep,name=expr_steps,json=exprSteps,proto3" json:"expr_steps,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
ExprSteps []*Explain_ExprStep `protobuf:"bytes,2,rep,name=expr_steps,json=exprSteps,proto3" json:"expr_steps,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Explain) Reset() {
|
||||
*x = Explain{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Explain) String() string {
|
||||
@@ -47,7 +45,7 @@ func (*Explain) ProtoMessage() {}
|
||||
|
||||
func (x *Explain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -77,21 +75,18 @@ func (x *Explain) GetExprSteps() []*Explain_ExprStep {
|
||||
}
|
||||
|
||||
type Explain_ExprStep struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ValueIndex int32 `protobuf:"varint,2,opt,name=value_index,json=valueIndex,proto3" json:"value_index,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ValueIndex int32 `protobuf:"varint,2,opt,name=value_index,json=valueIndex,proto3" json:"value_index,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Explain_ExprStep) Reset() {
|
||||
*x = Explain_ExprStep{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Explain_ExprStep) String() string {
|
||||
@@ -102,7 +97,7 @@ func (*Explain_ExprStep) ProtoMessage() {}
|
||||
|
||||
func (x *Explain_ExprStep) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_explain_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -133,42 +128,33 @@ func (x *Explain_ExprStep) GetValueIndex() int32 {
|
||||
|
||||
var File_cel_expr_explain_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_cel_expr_explain_proto_rawDesc = []byte{
|
||||
0x0a, 0x16, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x61,
|
||||
0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78,
|
||||
0x70, 0x72, 0x1a, 0x14, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae, 0x01, 0x0a, 0x07, 0x45, 0x78, 0x70,
|
||||
0x6c, 0x61, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x39, 0x0a,
|
||||
0x0a, 0x65, 0x78, 0x70, 0x72, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x78, 0x70,
|
||||
0x6c, 0x61, 0x69, 0x6e, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x53, 0x74, 0x65, 0x70, 0x52, 0x09, 0x65,
|
||||
0x78, 0x70, 0x72, 0x53, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x3b, 0x0a, 0x08, 0x45, 0x78, 0x70, 0x72,
|
||||
0x53, 0x74, 0x65, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e,
|
||||
0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x49, 0x6e, 0x64, 0x65, 0x78, 0x3a, 0x02, 0x18, 0x01, 0x42, 0x2f, 0x0a, 0x0c, 0x64, 0x65, 0x76,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0c, 0x45, 0x78, 0x70, 0x6c, 0x61,
|
||||
0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x0c, 0x63, 0x65, 0x6c, 0x2e, 0x64,
|
||||
0x65, 0x76, 0x2f, 0x65, 0x78, 0x70, 0x72, 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
const file_cel_expr_explain_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x16cel/expr/explain.proto\x12\bcel.expr\x1a\x14cel/expr/value.proto\"\xae\x01\n" +
|
||||
"\aExplain\x12'\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x0f.cel.expr.ValueR\x06values\x129\n" +
|
||||
"\n" +
|
||||
"expr_steps\x18\x02 \x03(\v2\x1a.cel.expr.Explain.ExprStepR\texprSteps\x1a;\n" +
|
||||
"\bExprStep\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1f\n" +
|
||||
"\vvalue_index\x18\x02 \x01(\x05R\n" +
|
||||
"valueIndex:\x02\x18\x01B/\n" +
|
||||
"\fdev.cel.exprB\fExplainProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_explain_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_explain_proto_rawDescData = file_cel_expr_explain_proto_rawDesc
|
||||
file_cel_expr_explain_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_explain_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_explain_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_explain_proto_rawDescData = protoimpl.X.CompressGZIP(file_cel_expr_explain_proto_rawDescData)
|
||||
file_cel_expr_explain_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_explain_proto_rawDesc), len(file_cel_expr_explain_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_explain_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_cel_expr_explain_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_cel_expr_explain_proto_goTypes = []interface{}{
|
||||
var file_cel_expr_explain_proto_goTypes = []any{
|
||||
(*Explain)(nil), // 0: cel.expr.Explain
|
||||
(*Explain_ExprStep)(nil), // 1: cel.expr.Explain.ExprStep
|
||||
(*Value)(nil), // 2: cel.expr.Value
|
||||
@@ -189,37 +175,11 @@ func file_cel_expr_explain_proto_init() {
|
||||
return
|
||||
}
|
||||
file_cel_expr_value_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_cel_expr_explain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Explain); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_explain_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Explain_ExprStep); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_cel_expr_explain_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_explain_proto_rawDesc), len(file_cel_expr_explain_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
@@ -230,7 +190,6 @@ func file_cel_expr_explain_proto_init() {
|
||||
MessageInfos: file_cel_expr_explain_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_explain_proto = out.File
|
||||
file_cel_expr_explain_proto_rawDesc = nil
|
||||
file_cel_expr_explain_proto_goTypes = nil
|
||||
file_cel_expr_explain_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+320
-559
File diff suppressed because it is too large
Load Diff
+135
-213
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.5
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/value.proto
|
||||
|
||||
package expr
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,11 +24,8 @@ const (
|
||||
)
|
||||
|
||||
type Value struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Kind:
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Kind:
|
||||
//
|
||||
// *Value_NullValue
|
||||
// *Value_BoolValue
|
||||
@@ -41,16 +39,16 @@ type Value struct {
|
||||
// *Value_MapValue
|
||||
// *Value_ListValue
|
||||
// *Value_TypeValue
|
||||
Kind isValue_Kind `protobuf_oneof:"kind"`
|
||||
Kind isValue_Kind `protobuf_oneof:"kind"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Value) Reset() {
|
||||
*x = Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Value) String() string {
|
||||
@@ -61,7 +59,7 @@ func (*Value) ProtoMessage() {}
|
||||
|
||||
func (x *Value) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -76,93 +74,117 @@ func (*Value) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_value_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *Value) GetKind() isValue_Kind {
|
||||
if m != nil {
|
||||
return m.Kind
|
||||
func (x *Value) GetKind() isValue_Kind {
|
||||
if x != nil {
|
||||
return x.Kind
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetNullValue() structpb.NullValue {
|
||||
if x, ok := x.GetKind().(*Value_NullValue); ok {
|
||||
return x.NullValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_NullValue); ok {
|
||||
return x.NullValue
|
||||
}
|
||||
}
|
||||
return structpb.NullValue(0)
|
||||
}
|
||||
|
||||
func (x *Value) GetBoolValue() bool {
|
||||
if x, ok := x.GetKind().(*Value_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Value) GetInt64Value() int64 {
|
||||
if x, ok := x.GetKind().(*Value_Int64Value); ok {
|
||||
return x.Int64Value
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_Int64Value); ok {
|
||||
return x.Int64Value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Value) GetUint64Value() uint64 {
|
||||
if x, ok := x.GetKind().(*Value_Uint64Value); ok {
|
||||
return x.Uint64Value
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_Uint64Value); ok {
|
||||
return x.Uint64Value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Value) GetDoubleValue() float64 {
|
||||
if x, ok := x.GetKind().(*Value_DoubleValue); ok {
|
||||
return x.DoubleValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_DoubleValue); ok {
|
||||
return x.DoubleValue
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Value) GetStringValue() string {
|
||||
if x, ok := x.GetKind().(*Value_StringValue); ok {
|
||||
return x.StringValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_StringValue); ok {
|
||||
return x.StringValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Value) GetBytesValue() []byte {
|
||||
if x, ok := x.GetKind().(*Value_BytesValue); ok {
|
||||
return x.BytesValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_BytesValue); ok {
|
||||
return x.BytesValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetEnumValue() *EnumValue {
|
||||
if x, ok := x.GetKind().(*Value_EnumValue); ok {
|
||||
return x.EnumValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_EnumValue); ok {
|
||||
return x.EnumValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetObjectValue() *anypb.Any {
|
||||
if x, ok := x.GetKind().(*Value_ObjectValue); ok {
|
||||
return x.ObjectValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_ObjectValue); ok {
|
||||
return x.ObjectValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetMapValue() *MapValue {
|
||||
if x, ok := x.GetKind().(*Value_MapValue); ok {
|
||||
return x.MapValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_MapValue); ok {
|
||||
return x.MapValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetListValue() *ListValue {
|
||||
if x, ok := x.GetKind().(*Value_ListValue); ok {
|
||||
return x.ListValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_ListValue); ok {
|
||||
return x.ListValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) GetTypeValue() string {
|
||||
if x, ok := x.GetKind().(*Value_TypeValue); ok {
|
||||
return x.TypeValue
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*Value_TypeValue); ok {
|
||||
return x.TypeValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -244,21 +266,18 @@ func (*Value_ListValue) isValue_Kind() {}
|
||||
func (*Value_TypeValue) isValue_Kind() {}
|
||||
|
||||
type EnumValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EnumValue) Reset() {
|
||||
*x = EnumValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EnumValue) String() string {
|
||||
@@ -269,7 +288,7 @@ func (*EnumValue) ProtoMessage() {}
|
||||
|
||||
func (x *EnumValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -299,20 +318,17 @@ func (x *EnumValue) GetValue() int32 {
|
||||
}
|
||||
|
||||
type ListValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListValue) Reset() {
|
||||
*x = ListValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListValue) String() string {
|
||||
@@ -323,7 +339,7 @@ func (*ListValue) ProtoMessage() {}
|
||||
|
||||
func (x *ListValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -346,20 +362,17 @@ func (x *ListValue) GetValues() []*Value {
|
||||
}
|
||||
|
||||
type MapValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entries []*MapValue_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Entries []*MapValue_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *MapValue) Reset() {
|
||||
*x = MapValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MapValue) String() string {
|
||||
@@ -370,7 +383,7 @@ func (*MapValue) ProtoMessage() {}
|
||||
|
||||
func (x *MapValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -393,21 +406,18 @@ func (x *MapValue) GetEntries() []*MapValue_Entry {
|
||||
}
|
||||
|
||||
type MapValue_Entry struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key *Value `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value *Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key *Value `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value *Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *MapValue_Entry) Reset() {
|
||||
*x = MapValue_Entry{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_cel_expr_value_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MapValue_Entry) String() string {
|
||||
@@ -418,7 +428,7 @@ func (*MapValue_Entry) ProtoMessage() {}
|
||||
|
||||
func (x *MapValue_Entry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_value_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -449,83 +459,58 @@ func (x *MapValue_Entry) GetValue() *Value {
|
||||
|
||||
var File_cel_expr_value_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_cel_expr_value_proto_rawDesc = []byte{
|
||||
0x0a, 0x14, 0x63, 0x65, 0x6c, 0x2f, 0x65, 0x78, 0x70, 0x72, 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72,
|
||||
0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72,
|
||||
0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9d, 0x04, 0x0a, 0x05, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x3b, 0x0a, 0x0a, 0x6e, 0x75, 0x6c, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4e, 0x75, 0x6c, 0x6c, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6e, 0x75, 0x6c, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x0b, 0x75, 0x69,
|
||||
0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x64, 0x6f, 0x75,
|
||||
0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x48,
|
||||
0x00, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23,
|
||||
0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65,
|
||||
0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x65, 0x6c,
|
||||
0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48,
|
||||
0x00, 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x39, 0x0a, 0x0c,
|
||||
0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x62, 0x6a, 0x65,
|
||||
0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x6d, 0x61, 0x70, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x65, 0x6c,
|
||||
0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x4d, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00,
|
||||
0x52, 0x08, 0x6d, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6c, 0x69,
|
||||
0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
|
||||
0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x0a, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0f,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x74, 0x79, 0x70, 0x65, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x35, 0x0a, 0x09, 0x45, 0x6e, 0x75,
|
||||
0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x22, 0x34, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a,
|
||||
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e,
|
||||
0x4d, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07,
|
||||
0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x51, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x12, 0x21, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||
0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x2e, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2d, 0x0a, 0x0c, 0x64, 0x65,
|
||||
0x76, 0x2e, 0x63, 0x65, 0x6c, 0x2e, 0x65, 0x78, 0x70, 0x72, 0x42, 0x0a, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x0c, 0x63, 0x65, 0x6c, 0x2e, 0x64, 0x65,
|
||||
0x76, 0x2f, 0x65, 0x78, 0x70, 0x72, 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
const file_cel_expr_value_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14cel/expr/value.proto\x12\bcel.expr\x1a\x19google/protobuf/any.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x9d\x04\n" +
|
||||
"\x05Value\x12;\n" +
|
||||
"\n" +
|
||||
"null_value\x18\x01 \x01(\x0e2\x1a.google.protobuf.NullValueH\x00R\tnullValue\x12\x1f\n" +
|
||||
"\n" +
|
||||
"bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12!\n" +
|
||||
"\vint64_value\x18\x03 \x01(\x03H\x00R\n" +
|
||||
"int64Value\x12#\n" +
|
||||
"\fuint64_value\x18\x04 \x01(\x04H\x00R\vuint64Value\x12#\n" +
|
||||
"\fdouble_value\x18\x05 \x01(\x01H\x00R\vdoubleValue\x12#\n" +
|
||||
"\fstring_value\x18\x06 \x01(\tH\x00R\vstringValue\x12!\n" +
|
||||
"\vbytes_value\x18\a \x01(\fH\x00R\n" +
|
||||
"bytesValue\x124\n" +
|
||||
"\n" +
|
||||
"enum_value\x18\t \x01(\v2\x13.cel.expr.EnumValueH\x00R\tenumValue\x129\n" +
|
||||
"\fobject_value\x18\n" +
|
||||
" \x01(\v2\x14.google.protobuf.AnyH\x00R\vobjectValue\x121\n" +
|
||||
"\tmap_value\x18\v \x01(\v2\x12.cel.expr.MapValueH\x00R\bmapValue\x124\n" +
|
||||
"\n" +
|
||||
"list_value\x18\f \x01(\v2\x13.cel.expr.ListValueH\x00R\tlistValue\x12\x1f\n" +
|
||||
"\n" +
|
||||
"type_value\x18\x0f \x01(\tH\x00R\ttypeValueB\x06\n" +
|
||||
"\x04kind\"5\n" +
|
||||
"\tEnumValue\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x05R\x05value\"4\n" +
|
||||
"\tListValue\x12'\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x0f.cel.expr.ValueR\x06values\"\x91\x01\n" +
|
||||
"\bMapValue\x122\n" +
|
||||
"\aentries\x18\x01 \x03(\v2\x18.cel.expr.MapValue.EntryR\aentries\x1aQ\n" +
|
||||
"\x05Entry\x12!\n" +
|
||||
"\x03key\x18\x01 \x01(\v2\x0f.cel.expr.ValueR\x03key\x12%\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x0f.cel.expr.ValueR\x05valueB-\n" +
|
||||
"\fdev.cel.exprB\n" +
|
||||
"ValueProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_value_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_value_proto_rawDescData = file_cel_expr_value_proto_rawDesc
|
||||
file_cel_expr_value_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_value_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_value_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_value_proto_rawDescData = protoimpl.X.CompressGZIP(file_cel_expr_value_proto_rawDescData)
|
||||
file_cel_expr_value_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_value_proto_rawDesc), len(file_cel_expr_value_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_value_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_cel_expr_value_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_cel_expr_value_proto_goTypes = []interface{}{
|
||||
var file_cel_expr_value_proto_goTypes = []any{
|
||||
(*Value)(nil), // 0: cel.expr.Value
|
||||
(*EnumValue)(nil), // 1: cel.expr.EnumValue
|
||||
(*ListValue)(nil), // 2: cel.expr.ListValue
|
||||
@@ -556,69 +541,7 @@ func file_cel_expr_value_proto_init() {
|
||||
if File_cel_expr_value_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_cel_expr_value_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Value); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*EnumValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ListValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MapValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MapValue_Entry); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_cel_expr_value_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||
file_cel_expr_value_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*Value_NullValue)(nil),
|
||||
(*Value_BoolValue)(nil),
|
||||
(*Value_Int64Value)(nil),
|
||||
@@ -636,7 +559,7 @@ func file_cel_expr_value_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_cel_expr_value_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_value_proto_rawDesc), len(file_cel_expr_value_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
@@ -647,7 +570,6 @@ func file_cel_expr_value_proto_init() {
|
||||
MessageInfos: file_cel_expr_value_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_value_proto = out.File
|
||||
file_cel_expr_value_proto_rawDesc = nil
|
||||
file_cel_expr_value_proto_goTypes = nil
|
||||
file_cel_expr_value_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+3
-1
@@ -28,7 +28,9 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
|
||||
}
|
||||
handleForwardResponseServerMetadata(w, mux, md)
|
||||
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
if !mux.disableChunkedEncoding {
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
}
|
||||
if err := handleForwardResponseOptions(ctx, w, nil, opts); err != nil {
|
||||
HTTPError(ctx, mux, marshaler, w, req, err)
|
||||
return
|
||||
|
||||
+11
@@ -73,6 +73,7 @@ type ServeMux struct {
|
||||
disablePathLengthFallback bool
|
||||
unescapingMode UnescapingMode
|
||||
writeContentLength bool
|
||||
disableChunkedEncoding bool
|
||||
}
|
||||
|
||||
// ServeMuxOption is an option that can be given to a ServeMux on construction.
|
||||
@@ -125,6 +126,16 @@ func WithMiddlewares(middlewares ...Middleware) ServeMuxOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisableChunkedEncoding disables the Transfer-Encoding: chunked header
|
||||
// for streaming responses. This is useful for streaming implementations that use
|
||||
// Content-Length, which is mutually exclusive with Transfer-Encoding:chunked.
|
||||
// Note that this option will not automatically add Content-Length headers, so it should be used with caution.
|
||||
func WithDisableChunkedEncoding() ServeMuxOption {
|
||||
return func(mux *ServeMux) {
|
||||
mux.disableChunkedEncoding = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetQueryParameterParser sets the query parameter parser, used to populate message from query parameters.
|
||||
// Configuring this will mean the generated OpenAPI output is no longer correct, and it should be
|
||||
// done with careful consideration.
|
||||
|
||||
Generated
Vendored
+30
@@ -199,3 +199,33 @@
|
||||
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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Generated
Vendored
+113
-193
@@ -4,23 +4,18 @@
|
||||
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/stats"
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/metric/noop"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeName is the instrumentation scope name.
|
||||
ScopeName = "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
// GRPCStatusCodeKey is convention for numeric status code of a gRPC request.
|
||||
GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code")
|
||||
)
|
||||
// ScopeName is the instrumentation scope name.
|
||||
const ScopeName = "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
// InterceptorFilter is a predicate used to determine whether a given request in
|
||||
// interceptor info should be instrumented. A InterceptorFilter must return true if
|
||||
@@ -36,26 +31,22 @@ type Filter func(*stats.RPCTagInfo) bool
|
||||
|
||||
// config is a group of options for this instrumentation.
|
||||
type config struct {
|
||||
Filter Filter
|
||||
InterceptorFilter InterceptorFilter
|
||||
Propagators propagation.TextMapPropagator
|
||||
TracerProvider trace.TracerProvider
|
||||
MeterProvider metric.MeterProvider
|
||||
SpanStartOptions []trace.SpanStartOption
|
||||
SpanAttributes []attribute.KeyValue
|
||||
MetricAttributes []attribute.KeyValue
|
||||
Filter Filter
|
||||
InterceptorFilter InterceptorFilter
|
||||
Propagators propagation.TextMapPropagator
|
||||
TracerProvider trace.TracerProvider
|
||||
MeterProvider metric.MeterProvider
|
||||
SpanKind trace.SpanKind
|
||||
SpanStartOptions []trace.SpanStartOption
|
||||
SpanAttributes []attribute.KeyValue
|
||||
MetricAttributes []attribute.KeyValue
|
||||
MetricAttributesFn func(ctx context.Context) []attribute.KeyValue
|
||||
|
||||
PublicEndpoint bool
|
||||
PublicEndpointFn func(ctx context.Context, info *stats.RPCTagInfo) bool
|
||||
|
||||
ReceivedEvent bool
|
||||
SentEvent bool
|
||||
|
||||
tracer trace.Tracer
|
||||
meter metric.Meter
|
||||
|
||||
rpcDuration metric.Float64Histogram
|
||||
rpcInBytes metric.Int64Histogram
|
||||
rpcOutBytes metric.Int64Histogram
|
||||
rpcInMessages metric.Int64Histogram
|
||||
rpcOutMessages metric.Int64Histogram
|
||||
}
|
||||
|
||||
// Option applies an option value for a config.
|
||||
@@ -63,8 +54,14 @@ type Option interface {
|
||||
apply(*config)
|
||||
}
|
||||
|
||||
type optionFunc func(*config)
|
||||
|
||||
func (f optionFunc) apply(c *config) {
|
||||
f(c)
|
||||
}
|
||||
|
||||
// newConfig returns a config configured with all the passed Options.
|
||||
func newConfig(opts []Option, role string) *config {
|
||||
func newConfig(opts []Option) *config {
|
||||
c := &config{
|
||||
Propagators: otel.GetTextMapPropagator(),
|
||||
TracerProvider: otel.GetTracerProvider(),
|
||||
@@ -73,162 +70,77 @@ func newConfig(opts []Option, role string) *config {
|
||||
for _, o := range opts {
|
||||
o.apply(c)
|
||||
}
|
||||
|
||||
c.tracer = c.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(SemVersion()),
|
||||
)
|
||||
|
||||
c.meter = c.MeterProvider.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
c.rpcDuration, err = c.meter.Float64Histogram("rpc."+role+".duration",
|
||||
metric.WithDescription("Measures the duration of inbound RPC."),
|
||||
metric.WithUnit("ms"))
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
if c.rpcDuration == nil {
|
||||
c.rpcDuration = noop.Float64Histogram{}
|
||||
}
|
||||
}
|
||||
|
||||
rpcRequestSize, err := c.meter.Int64Histogram("rpc."+role+".request.size",
|
||||
metric.WithDescription("Measures size of RPC request messages (uncompressed)."),
|
||||
metric.WithUnit("By"))
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
if rpcRequestSize == nil {
|
||||
rpcRequestSize = noop.Int64Histogram{}
|
||||
}
|
||||
}
|
||||
|
||||
rpcResponseSize, err := c.meter.Int64Histogram("rpc."+role+".response.size",
|
||||
metric.WithDescription("Measures size of RPC response messages (uncompressed)."),
|
||||
metric.WithUnit("By"))
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
if rpcResponseSize == nil {
|
||||
rpcResponseSize = noop.Int64Histogram{}
|
||||
}
|
||||
}
|
||||
|
||||
rpcRequestsPerRPC, err := c.meter.Int64Histogram("rpc."+role+".requests_per_rpc",
|
||||
metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
|
||||
metric.WithUnit("{count}"))
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
if rpcRequestsPerRPC == nil {
|
||||
rpcRequestsPerRPC = noop.Int64Histogram{}
|
||||
}
|
||||
}
|
||||
|
||||
rpcResponsesPerRPC, err := c.meter.Int64Histogram("rpc."+role+".responses_per_rpc",
|
||||
metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
|
||||
metric.WithUnit("{count}"))
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
if rpcResponsesPerRPC == nil {
|
||||
rpcResponsesPerRPC = noop.Int64Histogram{}
|
||||
}
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "client":
|
||||
c.rpcInBytes = rpcResponseSize
|
||||
c.rpcInMessages = rpcResponsesPerRPC
|
||||
c.rpcOutBytes = rpcRequestSize
|
||||
c.rpcOutMessages = rpcRequestsPerRPC
|
||||
case "server":
|
||||
c.rpcInBytes = rpcRequestSize
|
||||
c.rpcInMessages = rpcRequestsPerRPC
|
||||
c.rpcOutBytes = rpcResponseSize
|
||||
c.rpcOutMessages = rpcResponsesPerRPC
|
||||
default:
|
||||
c.rpcInBytes = noop.Int64Histogram{}
|
||||
c.rpcInMessages = noop.Int64Histogram{}
|
||||
c.rpcOutBytes = noop.Int64Histogram{}
|
||||
c.rpcOutMessages = noop.Int64Histogram{}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type propagatorsOption struct{ p propagation.TextMapPropagator }
|
||||
// WithPublicEndpoint configures the Handler to link the span with an incoming
|
||||
// span context. If this option is not provided, then the association is a child
|
||||
// association instead of a link.
|
||||
func WithPublicEndpoint() Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.PublicEndpoint = true
|
||||
})
|
||||
}
|
||||
|
||||
func (o propagatorsOption) apply(c *config) {
|
||||
if o.p != nil {
|
||||
c.Propagators = o.p
|
||||
}
|
||||
// WithPublicEndpointFn runs with every request, and allows conditionally
|
||||
// configuring the Handler to link the span with an incoming span context. If
|
||||
// this option is not provided or returns false, then the association is a
|
||||
// child association instead of a link.
|
||||
// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn.
|
||||
func WithPublicEndpointFn(fn func(context.Context, *stats.RPCTagInfo) bool) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.PublicEndpointFn = fn
|
||||
})
|
||||
}
|
||||
|
||||
// WithPropagators returns an Option to use the Propagators when extracting
|
||||
// and injecting trace context from requests.
|
||||
func WithPropagators(p propagation.TextMapPropagator) Option {
|
||||
return propagatorsOption{p: p}
|
||||
}
|
||||
|
||||
type tracerProviderOption struct{ tp trace.TracerProvider }
|
||||
|
||||
func (o tracerProviderOption) apply(c *config) {
|
||||
if o.tp != nil {
|
||||
c.TracerProvider = o.tp
|
||||
}
|
||||
return optionFunc(func(c *config) {
|
||||
if p != nil {
|
||||
c.Propagators = p
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithInterceptorFilter returns an Option to use the request filter.
|
||||
//
|
||||
// Deprecated: Use stats handlers instead.
|
||||
func WithInterceptorFilter(f InterceptorFilter) Option {
|
||||
return interceptorFilterOption{f: f}
|
||||
}
|
||||
|
||||
type interceptorFilterOption struct {
|
||||
f InterceptorFilter
|
||||
}
|
||||
|
||||
func (o interceptorFilterOption) apply(c *config) {
|
||||
if o.f != nil {
|
||||
c.InterceptorFilter = o.f
|
||||
}
|
||||
return optionFunc(func(c *config) {
|
||||
if f != nil {
|
||||
c.InterceptorFilter = f
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithFilter returns an Option to use the request filter.
|
||||
func WithFilter(f Filter) Option {
|
||||
return filterOption{f: f}
|
||||
}
|
||||
|
||||
type filterOption struct {
|
||||
f Filter
|
||||
}
|
||||
|
||||
func (o filterOption) apply(c *config) {
|
||||
if o.f != nil {
|
||||
c.Filter = o.f
|
||||
}
|
||||
return optionFunc(func(c *config) {
|
||||
if f != nil {
|
||||
c.Filter = f
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithTracerProvider returns an Option to use the TracerProvider when
|
||||
// creating a Tracer.
|
||||
func WithTracerProvider(tp trace.TracerProvider) Option {
|
||||
return tracerProviderOption{tp: tp}
|
||||
}
|
||||
|
||||
type meterProviderOption struct{ mp metric.MeterProvider }
|
||||
|
||||
func (o meterProviderOption) apply(c *config) {
|
||||
if o.mp != nil {
|
||||
c.MeterProvider = o.mp
|
||||
}
|
||||
return optionFunc(func(c *config) {
|
||||
if tp != nil {
|
||||
c.TracerProvider = tp
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithMeterProvider returns an Option to use the MeterProvider when
|
||||
// creating a Meter. If this option is not provide the global MeterProvider will be used.
|
||||
func WithMeterProvider(mp metric.MeterProvider) Option {
|
||||
return meterProviderOption{mp: mp}
|
||||
return optionFunc(func(c *config) {
|
||||
if mp != nil {
|
||||
c.MeterProvider = mp
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Event type that can be recorded, see WithMessageEvents.
|
||||
@@ -240,21 +152,6 @@ const (
|
||||
SentEvents
|
||||
)
|
||||
|
||||
type messageEventsProviderOption struct {
|
||||
events []Event
|
||||
}
|
||||
|
||||
func (m messageEventsProviderOption) apply(c *config) {
|
||||
for _, e := range m.events {
|
||||
switch e {
|
||||
case ReceivedEvents:
|
||||
c.ReceivedEvent = true
|
||||
case SentEvents:
|
||||
c.SentEvent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessageEvents configures the Handler to record the specified events
|
||||
// (span.AddEvent) on spans. By default only summary attributes are added at the
|
||||
// end of the request.
|
||||
@@ -263,43 +160,66 @@ func (m messageEventsProviderOption) apply(c *config) {
|
||||
// - ReceivedEvents: Record the number of bytes read after every gRPC read operation.
|
||||
// - SentEvents: Record the number of bytes written after every gRPC write operation.
|
||||
func WithMessageEvents(events ...Event) Option {
|
||||
return messageEventsProviderOption{events: events}
|
||||
}
|
||||
|
||||
type spanStartOption struct{ opts []trace.SpanStartOption }
|
||||
|
||||
func (o spanStartOption) apply(c *config) {
|
||||
c.SpanStartOptions = append(c.SpanStartOptions, o.opts...)
|
||||
return optionFunc(func(c *config) {
|
||||
for _, e := range events {
|
||||
switch e {
|
||||
case ReceivedEvents:
|
||||
c.ReceivedEvent = true
|
||||
case SentEvents:
|
||||
c.SentEvent = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpanOptions configures an additional set of
|
||||
// trace.SpanOptions, which are applied to each new span.
|
||||
//
|
||||
// Deprecated: It is only used by the deprecated interceptor, and is unused by [NewClientHandler] and [NewServerHandler].
|
||||
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
|
||||
return spanStartOption{opts}
|
||||
return optionFunc(func(c *config) {
|
||||
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
type spanAttributesOption struct{ a []attribute.KeyValue }
|
||||
|
||||
func (o spanAttributesOption) apply(c *config) {
|
||||
if o.a != nil {
|
||||
c.SpanAttributes = o.a
|
||||
}
|
||||
// WithSpanKind returns an Option to set the span kind for spans created by
|
||||
// the handler.
|
||||
//
|
||||
// By default, [NewServerHandler] creates spans with
|
||||
// [trace.SpanKindServer] and [NewClientHandler] creates spans with
|
||||
// [trace.SpanKindClient].
|
||||
func WithSpanKind(sk trace.SpanKind) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.SpanKind = sk
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpanAttributes returns an Option to add custom attributes to the spans.
|
||||
func WithSpanAttributes(a ...attribute.KeyValue) Option {
|
||||
return spanAttributesOption{a: a}
|
||||
}
|
||||
|
||||
type metricAttributesOption struct{ a []attribute.KeyValue }
|
||||
|
||||
func (o metricAttributesOption) apply(c *config) {
|
||||
if o.a != nil {
|
||||
c.MetricAttributes = o.a
|
||||
}
|
||||
return optionFunc(func(c *config) {
|
||||
if a != nil {
|
||||
c.SpanAttributes = a
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithMetricAttributes returns an Option to add custom attributes to the metrics.
|
||||
func WithMetricAttributes(a ...attribute.KeyValue) Option {
|
||||
return metricAttributesOption{a: a}
|
||||
return optionFunc(func(c *config) {
|
||||
if a != nil {
|
||||
c.MetricAttributes = append(c.MetricAttributes, a...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithMetricAttributesFn returns an Option to add dynamic custom attributes to the handler's metrics.
|
||||
// The function is called once per RPC and the returned attributes are applied to all metrics recorded by this handler.
|
||||
//
|
||||
// The context parameter is the standard gRPC request context and provides access to request-scoped data.
|
||||
func WithMetricAttributesFn(fn func(ctx context.Context) []attribute.KeyValue) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
if fn != nil {
|
||||
c.MetricAttributesFn = fn
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Generated
Vendored
+14
-487
@@ -4,506 +4,33 @@
|
||||
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
// gRPC tracing middleware
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md
|
||||
// https://opentelemetry.io/docs/specs/semconv/rpc/
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
grpc_codes "google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
grpc_codes "google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type messageType attribute.KeyValue
|
||||
|
||||
// Event adds an event of the messageType to the span associated with the
|
||||
// passed context with a message id.
|
||||
func (m messageType) Event(ctx context.Context, id int, _ interface{}) {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if !span.IsRecording() {
|
||||
return
|
||||
}
|
||||
span.AddEvent("message", trace.WithAttributes(
|
||||
attribute.KeyValue(m),
|
||||
RPCMessageIDKey.Int(id),
|
||||
))
|
||||
}
|
||||
|
||||
var (
|
||||
messageSent = messageType(RPCMessageTypeSent)
|
||||
messageReceived = messageType(RPCMessageTypeReceived)
|
||||
)
|
||||
|
||||
// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable
|
||||
// for use in a grpc.NewClient call.
|
||||
//
|
||||
// Deprecated: Use [NewClientHandler] instead.
|
||||
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
|
||||
cfg := newConfig(opts, "client")
|
||||
tracer := cfg.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(Version()),
|
||||
)
|
||||
|
||||
return func(
|
||||
ctx context.Context,
|
||||
method string,
|
||||
req, reply interface{},
|
||||
cc *grpc.ClientConn,
|
||||
invoker grpc.UnaryInvoker,
|
||||
callOpts ...grpc.CallOption,
|
||||
) error {
|
||||
i := &InterceptorInfo{
|
||||
Method: method,
|
||||
Type: UnaryClient,
|
||||
}
|
||||
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
|
||||
return invoker(ctx, method, req, reply, cc, callOpts...)
|
||||
}
|
||||
|
||||
name, attr, _ := telemetryAttributes(method, cc.Target())
|
||||
|
||||
startOpts := append([]trace.SpanStartOption{
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(attr...),
|
||||
},
|
||||
cfg.SpanStartOptions...,
|
||||
)
|
||||
|
||||
ctx, span := tracer.Start(
|
||||
ctx,
|
||||
name,
|
||||
startOpts...,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
ctx = inject(ctx, cfg.Propagators)
|
||||
|
||||
if cfg.SentEvent {
|
||||
messageSent.Event(ctx, 1, req)
|
||||
}
|
||||
|
||||
err := invoker(ctx, method, req, reply, cc, callOpts...)
|
||||
|
||||
if cfg.ReceivedEvent {
|
||||
messageReceived.Event(ctx, 1, reply)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s, _ := status.FromError(err)
|
||||
span.SetStatus(codes.Error, s.Message())
|
||||
span.SetAttributes(statusCodeAttr(s.Code()))
|
||||
} else {
|
||||
span.SetAttributes(statusCodeAttr(grpc_codes.OK))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// clientStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and
|
||||
// SendMsg method call.
|
||||
type clientStream struct {
|
||||
grpc.ClientStream
|
||||
desc *grpc.StreamDesc
|
||||
|
||||
span trace.Span
|
||||
|
||||
receivedEvent bool
|
||||
sentEvent bool
|
||||
|
||||
receivedMessageID int
|
||||
sentMessageID int
|
||||
}
|
||||
|
||||
var _ = proto.Marshal
|
||||
|
||||
func (w *clientStream) RecvMsg(m interface{}) error {
|
||||
err := w.ClientStream.RecvMsg(m)
|
||||
|
||||
if err == nil && !w.desc.ServerStreams {
|
||||
w.endSpan(nil)
|
||||
} else if errors.Is(err, io.EOF) {
|
||||
w.endSpan(nil)
|
||||
} else if err != nil {
|
||||
w.endSpan(err)
|
||||
} else {
|
||||
w.receivedMessageID++
|
||||
|
||||
if w.receivedEvent {
|
||||
messageReceived.Event(w.Context(), w.receivedMessageID, m)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *clientStream) SendMsg(m interface{}) error {
|
||||
err := w.ClientStream.SendMsg(m)
|
||||
|
||||
w.sentMessageID++
|
||||
|
||||
if w.sentEvent {
|
||||
messageSent.Event(w.Context(), w.sentMessageID, m)
|
||||
}
|
||||
|
||||
// serverAddrAttrs returns the server address attributes for the hostport.
|
||||
func serverAddrAttrs(hostport string) []attribute.KeyValue {
|
||||
h, pStr, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
w.endSpan(err)
|
||||
// The server.address attribute is required.
|
||||
return []attribute.KeyValue{semconv.ServerAddress(hostport)}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *clientStream) Header() (metadata.MD, error) {
|
||||
md, err := w.ClientStream.Header()
|
||||
p, err := strconv.Atoi(pStr)
|
||||
if err != nil {
|
||||
w.endSpan(err)
|
||||
return []attribute.KeyValue{semconv.ServerAddress(h)}
|
||||
}
|
||||
|
||||
return md, err
|
||||
}
|
||||
|
||||
func (w *clientStream) CloseSend() error {
|
||||
err := w.ClientStream.CloseSend()
|
||||
if err != nil {
|
||||
w.endSpan(err)
|
||||
return []attribute.KeyValue{
|
||||
semconv.ServerAddress(h),
|
||||
semconv.ServerPort(p),
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc, span trace.Span, cfg *config) *clientStream {
|
||||
return &clientStream{
|
||||
ClientStream: s,
|
||||
span: span,
|
||||
desc: desc,
|
||||
receivedEvent: cfg.ReceivedEvent,
|
||||
sentEvent: cfg.SentEvent,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *clientStream) endSpan(err error) {
|
||||
if err != nil {
|
||||
s, _ := status.FromError(err)
|
||||
w.span.SetStatus(codes.Error, s.Message())
|
||||
w.span.SetAttributes(statusCodeAttr(s.Code()))
|
||||
} else {
|
||||
w.span.SetAttributes(statusCodeAttr(grpc_codes.OK))
|
||||
}
|
||||
|
||||
w.span.End()
|
||||
}
|
||||
|
||||
// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable
|
||||
// for use in a grpc.NewClient call.
|
||||
//
|
||||
// Deprecated: Use [NewClientHandler] instead.
|
||||
func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
|
||||
cfg := newConfig(opts, "client")
|
||||
tracer := cfg.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(Version()),
|
||||
)
|
||||
|
||||
return func(
|
||||
ctx context.Context,
|
||||
desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn,
|
||||
method string,
|
||||
streamer grpc.Streamer,
|
||||
callOpts ...grpc.CallOption,
|
||||
) (grpc.ClientStream, error) {
|
||||
i := &InterceptorInfo{
|
||||
Method: method,
|
||||
Type: StreamClient,
|
||||
}
|
||||
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
|
||||
return streamer(ctx, desc, cc, method, callOpts...)
|
||||
}
|
||||
|
||||
name, attr, _ := telemetryAttributes(method, cc.Target())
|
||||
|
||||
startOpts := append([]trace.SpanStartOption{
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(attr...),
|
||||
},
|
||||
cfg.SpanStartOptions...,
|
||||
)
|
||||
|
||||
ctx, span := tracer.Start(
|
||||
ctx,
|
||||
name,
|
||||
startOpts...,
|
||||
)
|
||||
|
||||
ctx = inject(ctx, cfg.Propagators)
|
||||
|
||||
s, err := streamer(ctx, desc, cc, method, callOpts...)
|
||||
if err != nil {
|
||||
grpcStatus, _ := status.FromError(err)
|
||||
span.SetStatus(codes.Error, grpcStatus.Message())
|
||||
span.SetAttributes(statusCodeAttr(grpcStatus.Code()))
|
||||
span.End()
|
||||
return s, err
|
||||
}
|
||||
stream := wrapClientStream(s, desc, span, cfg)
|
||||
return stream, nil
|
||||
}
|
||||
}
|
||||
|
||||
// UnaryServerInterceptor returns a grpc.UnaryServerInterceptor suitable
|
||||
// for use in a grpc.NewServer call.
|
||||
//
|
||||
// Deprecated: Use [NewServerHandler] instead.
|
||||
func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
|
||||
cfg := newConfig(opts, "server")
|
||||
tracer := cfg.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(Version()),
|
||||
)
|
||||
|
||||
return func(
|
||||
ctx context.Context,
|
||||
req interface{},
|
||||
info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler,
|
||||
) (interface{}, error) {
|
||||
i := &InterceptorInfo{
|
||||
UnaryServerInfo: info,
|
||||
Type: UnaryServer,
|
||||
}
|
||||
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
ctx = extract(ctx, cfg.Propagators)
|
||||
name, attr, metricAttrs := telemetryAttributes(info.FullMethod, peerFromCtx(ctx))
|
||||
|
||||
startOpts := append([]trace.SpanStartOption{
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
trace.WithAttributes(attr...),
|
||||
},
|
||||
cfg.SpanStartOptions...,
|
||||
)
|
||||
|
||||
ctx, span := tracer.Start(
|
||||
trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)),
|
||||
name,
|
||||
startOpts...,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
if cfg.ReceivedEvent {
|
||||
messageReceived.Event(ctx, 1, req)
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
|
||||
resp, err := handler(ctx, req)
|
||||
|
||||
s, _ := status.FromError(err)
|
||||
if err != nil {
|
||||
statusCode, msg := serverStatus(s)
|
||||
span.SetStatus(statusCode, msg)
|
||||
if cfg.SentEvent {
|
||||
messageSent.Event(ctx, 1, s.Proto())
|
||||
}
|
||||
} else {
|
||||
if cfg.SentEvent {
|
||||
messageSent.Event(ctx, 1, resp)
|
||||
}
|
||||
}
|
||||
grpcStatusCodeAttr := statusCodeAttr(s.Code())
|
||||
span.SetAttributes(grpcStatusCodeAttr)
|
||||
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
elapsedTime := float64(time.Since(before)) / float64(time.Millisecond)
|
||||
|
||||
metricAttrs = append(metricAttrs, grpcStatusCodeAttr)
|
||||
cfg.rpcDuration.Record(ctx, elapsedTime, metric.WithAttributeSet(attribute.NewSet(metricAttrs...)))
|
||||
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
// serverStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and
|
||||
// SendMsg method call.
|
||||
type serverStream struct {
|
||||
grpc.ServerStream
|
||||
ctx context.Context
|
||||
|
||||
receivedMessageID int
|
||||
sentMessageID int
|
||||
|
||||
receivedEvent bool
|
||||
sentEvent bool
|
||||
}
|
||||
|
||||
func (w *serverStream) Context() context.Context {
|
||||
return w.ctx
|
||||
}
|
||||
|
||||
func (w *serverStream) RecvMsg(m interface{}) error {
|
||||
err := w.ServerStream.RecvMsg(m)
|
||||
|
||||
if err == nil {
|
||||
w.receivedMessageID++
|
||||
if w.receivedEvent {
|
||||
messageReceived.Event(w.Context(), w.receivedMessageID, m)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *serverStream) SendMsg(m interface{}) error {
|
||||
err := w.ServerStream.SendMsg(m)
|
||||
|
||||
w.sentMessageID++
|
||||
if w.sentEvent {
|
||||
messageSent.Event(w.Context(), w.sentMessageID, m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func wrapServerStream(ctx context.Context, ss grpc.ServerStream, cfg *config) *serverStream {
|
||||
return &serverStream{
|
||||
ServerStream: ss,
|
||||
ctx: ctx,
|
||||
receivedEvent: cfg.ReceivedEvent,
|
||||
sentEvent: cfg.SentEvent,
|
||||
}
|
||||
}
|
||||
|
||||
// StreamServerInterceptor returns a grpc.StreamServerInterceptor suitable
|
||||
// for use in a grpc.NewServer call.
|
||||
//
|
||||
// Deprecated: Use [NewServerHandler] instead.
|
||||
func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
|
||||
cfg := newConfig(opts, "server")
|
||||
tracer := cfg.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(Version()),
|
||||
)
|
||||
|
||||
return func(
|
||||
srv interface{},
|
||||
ss grpc.ServerStream,
|
||||
info *grpc.StreamServerInfo,
|
||||
handler grpc.StreamHandler,
|
||||
) error {
|
||||
ctx := ss.Context()
|
||||
i := &InterceptorInfo{
|
||||
StreamServerInfo: info,
|
||||
Type: StreamServer,
|
||||
}
|
||||
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
|
||||
return handler(srv, wrapServerStream(ctx, ss, cfg))
|
||||
}
|
||||
|
||||
ctx = extract(ctx, cfg.Propagators)
|
||||
name, attr, _ := telemetryAttributes(info.FullMethod, peerFromCtx(ctx))
|
||||
|
||||
startOpts := append([]trace.SpanStartOption{
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
trace.WithAttributes(attr...),
|
||||
},
|
||||
cfg.SpanStartOptions...,
|
||||
)
|
||||
|
||||
ctx, span := tracer.Start(
|
||||
trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)),
|
||||
name,
|
||||
startOpts...,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
err := handler(srv, wrapServerStream(ctx, ss, cfg))
|
||||
if err != nil {
|
||||
s, _ := status.FromError(err)
|
||||
statusCode, msg := serverStatus(s)
|
||||
span.SetStatus(statusCode, msg)
|
||||
span.SetAttributes(statusCodeAttr(s.Code()))
|
||||
} else {
|
||||
span.SetAttributes(statusCodeAttr(grpc_codes.OK))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// telemetryAttributes returns a span name and span and metric attributes from
|
||||
// the gRPC method and peer address.
|
||||
func telemetryAttributes(fullMethod, peerAddress string) (string, []attribute.KeyValue, []attribute.KeyValue) {
|
||||
name, methodAttrs := internal.ParseFullMethod(fullMethod)
|
||||
peerAttrs := peerAttr(peerAddress)
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, 1+len(methodAttrs)+len(peerAttrs))
|
||||
attrs = append(attrs, RPCSystemGRPC)
|
||||
attrs = append(attrs, methodAttrs...)
|
||||
metricAttrs := attrs[:1+len(methodAttrs)]
|
||||
attrs = append(attrs, peerAttrs...)
|
||||
return name, attrs, metricAttrs
|
||||
}
|
||||
|
||||
// peerAttr returns attributes about the peer address.
|
||||
func peerAttr(addr string) []attribute.KeyValue {
|
||||
host, p, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var attr []attribute.KeyValue
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
attr = []attribute.KeyValue{
|
||||
semconv.NetSockPeerAddr(host),
|
||||
semconv.NetSockPeerPort(port),
|
||||
}
|
||||
} else {
|
||||
attr = []attribute.KeyValue{
|
||||
semconv.NetPeerName(host),
|
||||
semconv.NetPeerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
// peerFromCtx returns a peer address from a context, if one exists.
|
||||
func peerFromCtx(ctx context.Context) string {
|
||||
p, ok := peer.FromContext(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return p.Addr.String()
|
||||
}
|
||||
|
||||
// statusCodeAttr returns status code attribute based on given gRPC code.
|
||||
func statusCodeAttr(c grpc_codes.Code) attribute.KeyValue {
|
||||
return GRPCStatusCodeKey.Int64(int64(c))
|
||||
}
|
||||
|
||||
// serverStatus returns a span status code and message for a given gRPC
|
||||
|
||||
vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/parse.go
Generated
Vendored
+3
-16
@@ -1,13 +1,14 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionality for the otelgrpc package.
|
||||
package internal // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
)
|
||||
|
||||
// ParseFullMethod returns a span name following the OpenTelemetry semantic
|
||||
@@ -22,19 +23,5 @@ func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
|
||||
return fullMethod, nil
|
||||
}
|
||||
name := fullMethod[1:]
|
||||
pos := strings.LastIndex(name, "/")
|
||||
if pos < 0 {
|
||||
// Invalid format, does not follow `/package.service/method`.
|
||||
return name, nil
|
||||
}
|
||||
service, method := name[:pos], name[pos+1:]
|
||||
|
||||
var attrs []attribute.KeyValue
|
||||
if service != "" {
|
||||
attrs = append(attrs, semconv.RPCService(service))
|
||||
}
|
||||
if method != "" {
|
||||
attrs = append(attrs, semconv.RPCMethod(method))
|
||||
}
|
||||
return name, attrs
|
||||
return name, []attribute.KeyValue{semconv.RPCMethod(name)}
|
||||
}
|
||||
|
||||
Generated
Vendored
+12
-39
@@ -6,21 +6,18 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"go.opentelemetry.io/otel/baggage"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type metadataSupplier struct {
|
||||
metadata *metadata.MD
|
||||
metadata metadata.MD
|
||||
}
|
||||
|
||||
// assert that metadataSupplier implements the TextMapCarrier interface.
|
||||
var _ propagation.TextMapCarrier = &metadataSupplier{}
|
||||
var _ propagation.TextMapCarrier = metadataSupplier{}
|
||||
|
||||
func (s *metadataSupplier) Get(key string) string {
|
||||
func (s metadataSupplier) Get(key string) string {
|
||||
values := s.metadata.Get(key)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
@@ -28,51 +25,27 @@ func (s *metadataSupplier) Get(key string) string {
|
||||
return values[0]
|
||||
}
|
||||
|
||||
func (s *metadataSupplier) Set(key string, value string) {
|
||||
func (s metadataSupplier) Set(key, value string) {
|
||||
s.metadata.Set(key, value)
|
||||
}
|
||||
|
||||
func (s *metadataSupplier) Keys() []string {
|
||||
out := make([]string, 0, len(*s.metadata))
|
||||
for key := range *s.metadata {
|
||||
func (s metadataSupplier) Keys() []string {
|
||||
out := make([]string, 0, len(s.metadata))
|
||||
for key := range s.metadata {
|
||||
out = append(out, key)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Inject injects correlation context and span context into the gRPC
|
||||
// metadata object. This function is meant to be used on outgoing
|
||||
// requests.
|
||||
// Deprecated: Unnecessary public func.
|
||||
func Inject(ctx context.Context, md *metadata.MD, opts ...Option) {
|
||||
c := newConfig(opts, "")
|
||||
c.Propagators.Inject(ctx, &metadataSupplier{
|
||||
metadata: md,
|
||||
})
|
||||
}
|
||||
|
||||
func inject(ctx context.Context, propagators propagation.TextMapPropagator) context.Context {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.MD{}
|
||||
}
|
||||
propagators.Inject(ctx, &metadataSupplier{
|
||||
metadata: &md,
|
||||
})
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
// Extract returns the correlation context and span context that
|
||||
// another service encoded in the gRPC metadata object with Inject.
|
||||
// This function is meant to be used on incoming requests.
|
||||
// Deprecated: Unnecessary public func.
|
||||
func Extract(ctx context.Context, md *metadata.MD, opts ...Option) (baggage.Baggage, trace.SpanContext) {
|
||||
c := newConfig(opts, "")
|
||||
ctx = c.Propagators.Extract(ctx, &metadataSupplier{
|
||||
propagators.Inject(ctx, metadataSupplier{
|
||||
metadata: md,
|
||||
})
|
||||
|
||||
return baggage.FromContext(ctx), trace.SpanContextFromContext(ctx)
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
func extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context {
|
||||
@@ -81,7 +54,7 @@ func extract(ctx context.Context, propagators propagation.TextMapPropagator) con
|
||||
md = metadata.MD{}
|
||||
}
|
||||
|
||||
return propagators.Extract(ctx, &metadataSupplier{
|
||||
metadata: &md,
|
||||
return propagators.Extract(ctx, metadataSupplier{
|
||||
metadata: md,
|
||||
})
|
||||
}
|
||||
|
||||
Generated
Vendored
-41
@@ -1,41 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
)
|
||||
|
||||
// Semantic conventions for attribute keys for gRPC.
|
||||
const (
|
||||
// Name of message transmitted or received.
|
||||
RPCNameKey = attribute.Key("name")
|
||||
|
||||
// Type of message transmitted or received.
|
||||
RPCMessageTypeKey = attribute.Key("message.type")
|
||||
|
||||
// Identifier of message transmitted or received.
|
||||
RPCMessageIDKey = attribute.Key("message.id")
|
||||
|
||||
// The compressed size of the message transmitted or received in bytes.
|
||||
RPCMessageCompressedSizeKey = attribute.Key("message.compressed_size")
|
||||
|
||||
// The uncompressed size of the message transmitted or received in
|
||||
// bytes.
|
||||
RPCMessageUncompressedSizeKey = attribute.Key("message.uncompressed_size")
|
||||
)
|
||||
|
||||
// Semantic conventions for common RPC attributes.
|
||||
var (
|
||||
// Semantic convention for gRPC as the remoting system.
|
||||
RPCSystemGRPC = semconv.RPCSystemGRPC
|
||||
|
||||
// Semantic convention for a message named message.
|
||||
RPCNameMessage = RPCNameKey.String("message")
|
||||
|
||||
// Semantic conventions for RPC message types.
|
||||
RPCMessageTypeSent = RPCMessageTypeKey.String("SENT")
|
||||
RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED")
|
||||
)
|
||||
Generated
Vendored
+249
-77
@@ -5,97 +5,198 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.39.0/rpcconv"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
grpc_codes "google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/stats"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal"
|
||||
)
|
||||
|
||||
type gRPCContextKey struct{}
|
||||
|
||||
type gRPCContext struct {
|
||||
inMessages int64
|
||||
outMessages int64
|
||||
metricAttrs []attribute.KeyValue
|
||||
record bool
|
||||
inMessages int64
|
||||
outMessages int64
|
||||
metricAttrs []attribute.KeyValue
|
||||
metricAttrSet attribute.Set
|
||||
record bool
|
||||
}
|
||||
|
||||
type serverHandler struct {
|
||||
*config
|
||||
|
||||
tracer trace.Tracer
|
||||
|
||||
duration rpcconv.ServerCallDuration
|
||||
inSize int64Hist
|
||||
outSize int64Hist
|
||||
}
|
||||
|
||||
// NewServerHandler creates a stats.Handler for a gRPC server.
|
||||
func NewServerHandler(opts ...Option) stats.Handler {
|
||||
h := &serverHandler{
|
||||
config: newConfig(opts, "server"),
|
||||
c := newConfig(opts)
|
||||
if c.SpanKind == trace.SpanKindUnspecified {
|
||||
c.SpanKind = trace.SpanKindServer
|
||||
}
|
||||
|
||||
h := &serverHandler{config: c}
|
||||
|
||||
h.tracer = c.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(Version),
|
||||
)
|
||||
|
||||
meter := c.MeterProvider.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
h.duration, err = rpcconv.NewServerCallDuration(meter)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
|
||||
h.inSize, err = rpcconv.NewServerRequestSize(meter)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
|
||||
h.outSize, err = rpcconv.NewServerResponseSize(meter)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// TagConn can attach some information to the given context.
|
||||
func (h *serverHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
|
||||
func (*serverHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// HandleConn processes the Conn stats.
|
||||
func (h *serverHandler) HandleConn(ctx context.Context, info stats.ConnStats) {
|
||||
func (*serverHandler) HandleConn(context.Context, stats.ConnStats) {
|
||||
}
|
||||
|
||||
// TagRPC can attach some information to the given context.
|
||||
func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
|
||||
ctx = extract(ctx, h.config.Propagators)
|
||||
ctx = extract(ctx, h.Propagators)
|
||||
|
||||
name, attrs := internal.ParseFullMethod(info.FullMethodName)
|
||||
attrs = append(attrs, RPCSystemGRPC)
|
||||
attrs = append(attrs, semconv.RPCSystemNameGRPC)
|
||||
|
||||
record := true
|
||||
if h.config.Filter != nil {
|
||||
record = h.config.Filter(info)
|
||||
if h.Filter != nil {
|
||||
record = h.Filter(info)
|
||||
}
|
||||
|
||||
if record {
|
||||
// Make a new slice to avoid aliasing into the same attrs slice used by metrics.
|
||||
spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes))
|
||||
spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...)
|
||||
opts := []trace.SpanStartOption{
|
||||
trace.WithSpanKind(h.SpanKind),
|
||||
trace.WithAttributes(spanAttributes...),
|
||||
}
|
||||
if h.PublicEndpoint || (h.PublicEndpointFn != nil && h.PublicEndpointFn(ctx, info)) {
|
||||
opts = append(opts, trace.WithNewRoot())
|
||||
// Linking incoming span context if any for public endpoint.
|
||||
if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
|
||||
opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s}))
|
||||
}
|
||||
}
|
||||
ctx, _ = h.tracer.Start(
|
||||
trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)),
|
||||
name,
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
trace.WithAttributes(append(attrs, h.config.SpanAttributes...)...),
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
gctx := gRPCContext{
|
||||
metricAttrs: append(attrs, h.config.MetricAttributes...),
|
||||
metricAttrs: append(attrs, h.MetricAttributes...),
|
||||
record: record,
|
||||
}
|
||||
|
||||
if h.MetricAttributesFn != nil {
|
||||
extraAttrs := h.MetricAttributesFn(ctx)
|
||||
gctx.metricAttrs = append(gctx.metricAttrs, extraAttrs...)
|
||||
}
|
||||
|
||||
gctx.metricAttrSet = attribute.NewSet(gctx.metricAttrs...)
|
||||
|
||||
return context.WithValue(ctx, gRPCContextKey{}, &gctx)
|
||||
}
|
||||
|
||||
// HandleRPC processes the RPC stats.
|
||||
func (h *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
|
||||
isServer := true
|
||||
h.handleRPC(ctx, rs, isServer)
|
||||
h.handleRPC(
|
||||
ctx,
|
||||
rs,
|
||||
h.duration.Inst(),
|
||||
h.inSize,
|
||||
h.outSize,
|
||||
serverStatus,
|
||||
)
|
||||
}
|
||||
|
||||
type clientHandler struct {
|
||||
*config
|
||||
|
||||
tracer trace.Tracer
|
||||
|
||||
duration rpcconv.ClientCallDuration
|
||||
inSize int64Hist
|
||||
outSize int64Hist
|
||||
}
|
||||
|
||||
// NewClientHandler creates a stats.Handler for a gRPC client.
|
||||
func NewClientHandler(opts ...Option) stats.Handler {
|
||||
h := &clientHandler{
|
||||
config: newConfig(opts, "client"),
|
||||
c := newConfig(opts)
|
||||
if c.SpanKind == trace.SpanKindUnspecified {
|
||||
c.SpanKind = trace.SpanKindClient
|
||||
}
|
||||
|
||||
h := &clientHandler{config: c}
|
||||
|
||||
h.tracer = c.TracerProvider.Tracer(
|
||||
ScopeName,
|
||||
trace.WithInstrumentationVersion(Version),
|
||||
)
|
||||
|
||||
meter := c.MeterProvider.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
h.duration, err = rpcconv.NewClientCallDuration(meter)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
|
||||
h.inSize, err = rpcconv.NewClientResponseSize(meter)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
|
||||
h.outSize, err = rpcconv.NewClientRequestSize(meter)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
|
||||
return h
|
||||
@@ -104,131 +205,202 @@ func NewClientHandler(opts ...Option) stats.Handler {
|
||||
// TagRPC can attach some information to the given context.
|
||||
func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
|
||||
name, attrs := internal.ParseFullMethod(info.FullMethodName)
|
||||
attrs = append(attrs, RPCSystemGRPC)
|
||||
attrs = append(attrs, semconv.RPCSystemNameGRPC)
|
||||
|
||||
record := true
|
||||
if h.config.Filter != nil {
|
||||
record = h.config.Filter(info)
|
||||
if h.Filter != nil {
|
||||
record = h.Filter(info)
|
||||
}
|
||||
|
||||
if record {
|
||||
// Make a new slice to avoid aliasing into the same attrs slice used by metrics.
|
||||
spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes))
|
||||
spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...)
|
||||
ctx, _ = h.tracer.Start(
|
||||
ctx,
|
||||
name,
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(append(attrs, h.config.SpanAttributes...)...),
|
||||
trace.WithSpanKind(h.SpanKind),
|
||||
trace.WithAttributes(spanAttributes...),
|
||||
)
|
||||
}
|
||||
|
||||
gctx := gRPCContext{
|
||||
metricAttrs: append(attrs, h.config.MetricAttributes...),
|
||||
metricAttrs: append(attrs, h.MetricAttributes...),
|
||||
record: record,
|
||||
}
|
||||
|
||||
return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.config.Propagators)
|
||||
if h.MetricAttributesFn != nil {
|
||||
extraAttrs := h.MetricAttributesFn(ctx)
|
||||
gctx.metricAttrs = append(gctx.metricAttrs, extraAttrs...)
|
||||
}
|
||||
|
||||
gctx.metricAttrSet = attribute.NewSet(gctx.metricAttrs...)
|
||||
|
||||
return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.Propagators)
|
||||
}
|
||||
|
||||
// HandleRPC processes the RPC stats.
|
||||
func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
|
||||
isServer := false
|
||||
h.handleRPC(ctx, rs, isServer)
|
||||
h.handleRPC(
|
||||
ctx,
|
||||
rs,
|
||||
h.duration.Inst(),
|
||||
h.inSize,
|
||||
h.outSize,
|
||||
func(s *status.Status) (codes.Code, string) {
|
||||
return codes.Error, s.Message()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TagConn can attach some information to the given context.
|
||||
func (h *clientHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
|
||||
func (*clientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// HandleConn processes the Conn stats.
|
||||
func (h *clientHandler) HandleConn(context.Context, stats.ConnStats) {
|
||||
func (*clientHandler) HandleConn(context.Context, stats.ConnStats) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool) { // nolint: revive // isServer is not a control flag.
|
||||
span := trace.SpanFromContext(ctx)
|
||||
var metricAttrs []attribute.KeyValue
|
||||
var messageId int64
|
||||
type int64Hist interface {
|
||||
RecordSet(context.Context, int64, attribute.Set)
|
||||
}
|
||||
|
||||
func (c *config) handleRPC(
|
||||
ctx context.Context,
|
||||
rs stats.RPCStats,
|
||||
duration metric.Float64Histogram,
|
||||
inSize, outSize int64Hist,
|
||||
recordStatus func(*status.Status) (codes.Code, string),
|
||||
) {
|
||||
gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext)
|
||||
if gctx != nil {
|
||||
if !gctx.record {
|
||||
return
|
||||
}
|
||||
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
|
||||
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
|
||||
if gctx != nil && !gctx.record {
|
||||
return
|
||||
}
|
||||
|
||||
span := trace.SpanFromContext(ctx)
|
||||
var messageId int64
|
||||
|
||||
switch rs := rs.(type) {
|
||||
case *stats.Begin:
|
||||
case *stats.InPayload:
|
||||
if gctx != nil {
|
||||
messageId = atomic.AddInt64(&gctx.inMessages, 1)
|
||||
c.rpcInBytes.Record(ctx, int64(rs.Length), metric.WithAttributeSet(attribute.NewSet(metricAttrs...)))
|
||||
inSize.RecordSet(ctx, int64(rs.Length), gctx.metricAttrSet)
|
||||
}
|
||||
|
||||
if c.ReceivedEvent {
|
||||
if c.ReceivedEvent && span.IsRecording() {
|
||||
span.AddEvent("message",
|
||||
trace.WithAttributes(
|
||||
semconv.MessageTypeReceived,
|
||||
semconv.MessageIDKey.Int64(messageId),
|
||||
semconv.MessageCompressedSizeKey.Int(rs.CompressedLength),
|
||||
semconv.MessageUncompressedSizeKey.Int(rs.Length),
|
||||
semconv.RPCMessageTypeReceived,
|
||||
semconv.RPCMessageIDKey.Int64(messageId),
|
||||
semconv.RPCMessageCompressedSizeKey.Int(rs.CompressedLength),
|
||||
semconv.RPCMessageUncompressedSizeKey.Int(rs.Length),
|
||||
),
|
||||
)
|
||||
}
|
||||
case *stats.OutPayload:
|
||||
if gctx != nil {
|
||||
messageId = atomic.AddInt64(&gctx.outMessages, 1)
|
||||
c.rpcOutBytes.Record(ctx, int64(rs.Length), metric.WithAttributeSet(attribute.NewSet(metricAttrs...)))
|
||||
outSize.RecordSet(ctx, int64(rs.Length), gctx.metricAttrSet)
|
||||
}
|
||||
|
||||
if c.SentEvent {
|
||||
if c.SentEvent && span.IsRecording() {
|
||||
span.AddEvent("message",
|
||||
trace.WithAttributes(
|
||||
semconv.MessageTypeSent,
|
||||
semconv.MessageIDKey.Int64(messageId),
|
||||
semconv.MessageCompressedSizeKey.Int(rs.CompressedLength),
|
||||
semconv.MessageUncompressedSizeKey.Int(rs.Length),
|
||||
semconv.RPCMessageTypeSent,
|
||||
semconv.RPCMessageIDKey.Int64(messageId),
|
||||
semconv.RPCMessageCompressedSizeKey.Int(rs.CompressedLength),
|
||||
semconv.RPCMessageUncompressedSizeKey.Int(rs.Length),
|
||||
),
|
||||
)
|
||||
}
|
||||
case *stats.OutTrailer:
|
||||
case *stats.OutHeader:
|
||||
if p, ok := peer.FromContext(ctx); ok {
|
||||
span.SetAttributes(peerAttr(p.Addr.String())...)
|
||||
if span.IsRecording() {
|
||||
if p, ok := peer.FromContext(ctx); ok {
|
||||
span.SetAttributes(serverAddrAttrs(p.Addr.String())...)
|
||||
}
|
||||
}
|
||||
case *stats.End:
|
||||
var rpcStatusAttr attribute.KeyValue
|
||||
|
||||
var s *status.Status
|
||||
if rs.Error != nil {
|
||||
s, _ := status.FromError(rs.Error)
|
||||
if isServer {
|
||||
statusCode, msg := serverStatus(s)
|
||||
span.SetStatus(statusCode, msg)
|
||||
} else {
|
||||
span.SetStatus(codes.Error, s.Message())
|
||||
}
|
||||
rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(s.Code()))
|
||||
s, _ = status.FromError(rs.Error)
|
||||
rpcStatusAttr = semconv.RPCResponseStatusCode(canonicalString(s.Code()))
|
||||
} else {
|
||||
rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(grpc_codes.OK))
|
||||
rpcStatusAttr = semconv.RPCResponseStatusCode(canonicalString(grpc_codes.OK))
|
||||
}
|
||||
if span.IsRecording() {
|
||||
if s != nil {
|
||||
c, m := recordStatus(s)
|
||||
span.SetStatus(c, m)
|
||||
}
|
||||
span.SetAttributes(rpcStatusAttr)
|
||||
span.End()
|
||||
}
|
||||
span.SetAttributes(rpcStatusAttr)
|
||||
span.End()
|
||||
|
||||
var metricAttrs []attribute.KeyValue
|
||||
if gctx != nil {
|
||||
// Don't use gctx.metricAttrSet here, because it requires passing
|
||||
// multiple RecordOptions, which would call metric.mergeSets and
|
||||
// allocate a new set for each Record call.
|
||||
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
|
||||
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
|
||||
}
|
||||
metricAttrs = append(metricAttrs, rpcStatusAttr)
|
||||
// Allocate vararg slice once.
|
||||
recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))}
|
||||
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
// Measure right before calling Record() to capture as much elapsed time as possible.
|
||||
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Millisecond)
|
||||
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Second)
|
||||
|
||||
c.rpcDuration.Record(ctx, elapsedTime, recordOpts...)
|
||||
if gctx != nil {
|
||||
c.rpcInMessages.Record(ctx, atomic.LoadInt64(&gctx.inMessages), recordOpts...)
|
||||
c.rpcOutMessages.Record(ctx, atomic.LoadInt64(&gctx.outMessages), recordOpts...)
|
||||
}
|
||||
duration.Record(ctx, elapsedTime, recordOpts...)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalString(code grpc_codes.Code) string {
|
||||
switch code {
|
||||
case grpc_codes.OK:
|
||||
return "OK"
|
||||
case grpc_codes.Canceled:
|
||||
return "CANCELLED"
|
||||
case grpc_codes.Unknown:
|
||||
return "UNKNOWN"
|
||||
case grpc_codes.InvalidArgument:
|
||||
return "INVALID_ARGUMENT"
|
||||
case grpc_codes.DeadlineExceeded:
|
||||
return "DEADLINE_EXCEEDED"
|
||||
case grpc_codes.NotFound:
|
||||
return "NOT_FOUND"
|
||||
case grpc_codes.AlreadyExists:
|
||||
return "ALREADY_EXISTS"
|
||||
case grpc_codes.PermissionDenied:
|
||||
return "PERMISSION_DENIED"
|
||||
case grpc_codes.ResourceExhausted:
|
||||
return "RESOURCE_EXHAUSTED"
|
||||
case grpc_codes.FailedPrecondition:
|
||||
return "FAILED_PRECONDITION"
|
||||
case grpc_codes.Aborted:
|
||||
return "ABORTED"
|
||||
case grpc_codes.OutOfRange:
|
||||
return "OUT_OF_RANGE"
|
||||
case grpc_codes.Unimplemented:
|
||||
return "UNIMPLEMENTED"
|
||||
case grpc_codes.Internal:
|
||||
return "INTERNAL"
|
||||
case grpc_codes.Unavailable:
|
||||
return "UNAVAILABLE"
|
||||
case grpc_codes.DataLoss:
|
||||
return "DATA_LOSS"
|
||||
case grpc_codes.Unauthenticated:
|
||||
return "UNAUTHENTICATED"
|
||||
default:
|
||||
return "CODE(" + strconv.FormatInt(int64(code), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-11
@@ -4,14 +4,4 @@
|
||||
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
|
||||
// Version is the current release version of the gRPC instrumentation.
|
||||
func Version() string {
|
||||
return "0.60.0"
|
||||
// This string is updated by the pre_release.sh script during release
|
||||
}
|
||||
|
||||
// SemVersion is the semantic version to be supplied to tracer/meter creation.
|
||||
//
|
||||
// Deprecated: Use [Version] instead.
|
||||
func SemVersion() string {
|
||||
return Version()
|
||||
}
|
||||
const Version = "0.66.0"
|
||||
|
||||
+30
@@ -199,3 +199,33 @@
|
||||
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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultClient is the default Client and is used by Get, Head, Post and PostForm.
|
||||
// Please be careful of initialization order - for example, if you change
|
||||
// the global propagator, the DefaultClient might still be using the old one.
|
||||
var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)}
|
||||
|
||||
// Get is a convenient replacement for http.Get that adds a span around the request.
|
||||
func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// Head is a convenient replacement for http.Head that adds a span around the request.
|
||||
func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, targetURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// Post is a convenient replacement for http.Post that adds a span around the request.
|
||||
func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// PostForm is a convenient replacement for http.PostForm that adds a span around the request.
|
||||
func PostForm(ctx context.Context, targetURL string, data url.Values) (resp *http.Response, err error) {
|
||||
return Post(ctx, targetURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
+1
-1
@@ -23,5 +23,5 @@ const (
|
||||
type Filter func(*http.Request) bool
|
||||
|
||||
func newTracer(tp trace.TracerProvider) trace.Tracer {
|
||||
return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version()))
|
||||
return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version))
|
||||
}
|
||||
|
||||
+10
-17
@@ -8,9 +8,8 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -27,7 +26,6 @@ type config struct {
|
||||
Meter metric.Meter
|
||||
Propagators propagation.TextMapPropagator
|
||||
SpanStartOptions []trace.SpanStartOption
|
||||
PublicEndpoint bool
|
||||
PublicEndpointFn func(*http.Request) bool
|
||||
ReadEvent bool
|
||||
WriteEvent bool
|
||||
@@ -68,7 +66,7 @@ func newConfig(opts ...Option) *config {
|
||||
|
||||
c.Meter = c.MeterProvider.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version()),
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
)
|
||||
|
||||
return c
|
||||
@@ -94,20 +92,10 @@ func WithMeterProvider(provider metric.MeterProvider) Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithPublicEndpoint configures the Handler to link the span with an incoming
|
||||
// span context. If this option is not provided, then the association is a child
|
||||
// association instead of a link.
|
||||
func WithPublicEndpoint() Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.PublicEndpoint = true
|
||||
})
|
||||
}
|
||||
|
||||
// WithPublicEndpointFn runs with every request, and allows conditionally
|
||||
// configuring the Handler to link the span with an incoming span context. If
|
||||
// this option is not provided or returns false, then the association is a
|
||||
// child association instead of a link.
|
||||
// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn.
|
||||
func WithPublicEndpointFn(fn func(*http.Request) bool) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.PublicEndpointFn = fn
|
||||
@@ -144,11 +132,13 @@ func WithFilter(f Filter) Option {
|
||||
})
|
||||
}
|
||||
|
||||
type event int
|
||||
// Event represents message event types for [WithMessageEvents].
|
||||
type Event int
|
||||
|
||||
// Different types of events that can be recorded, see WithMessageEvents.
|
||||
const (
|
||||
ReadEvents event = iota
|
||||
unspecifiedEvents Event = iota
|
||||
ReadEvents
|
||||
WriteEvents
|
||||
)
|
||||
|
||||
@@ -161,7 +151,7 @@ const (
|
||||
// using the ReadBytesKey
|
||||
// - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
|
||||
// using the WriteBytesKey
|
||||
func WithMessageEvents(events ...event) Option {
|
||||
func WithMessageEvents(events ...Event) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
for _, e := range events {
|
||||
switch e {
|
||||
@@ -204,6 +194,9 @@ func WithServerName(server string) Option {
|
||||
|
||||
// WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue.
|
||||
// These attributes will be included in metrics for every request.
|
||||
//
|
||||
// Deprecated: WithMetricAttributesFn is deprecated and will be removed in a
|
||||
// future release. Use [Labeler] instead.
|
||||
func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.MetricAttributesFn = metricAttributesFn
|
||||
|
||||
+1
-2
@@ -2,6 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package otelhttp provides an http.Handler and functions that are intended
|
||||
// to be used to add tracing by wrapping existing handlers (with Handler) and
|
||||
// routes WithRouteTag.
|
||||
// to be used to add tracing by wrapping existing handlers.
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
+15
-36
@@ -8,13 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/felixge/httpsnoop"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
)
|
||||
|
||||
// middleware is an http middleware which wraps the next handler in a span.
|
||||
@@ -29,7 +29,6 @@ type middleware struct {
|
||||
writeEvent bool
|
||||
filters []Filter
|
||||
spanNameFormatter func(string, *http.Request) string
|
||||
publicEndpoint bool
|
||||
publicEndpointFn func(*http.Request) bool
|
||||
metricAttributesFn func(*http.Request) []attribute.KeyValue
|
||||
|
||||
@@ -77,7 +76,6 @@ func (h *middleware) configure(c *config) {
|
||||
h.writeEvent = c.WriteEvent
|
||||
h.filters = c.Filters
|
||||
h.spanNameFormatter = c.SpanNameFormatter
|
||||
h.publicEndpoint = c.PublicEndpoint
|
||||
h.publicEndpointFn = c.PublicEndpointFn
|
||||
h.server = c.ServerName
|
||||
h.semconv = semconv.NewHTTPServer(c.Meter)
|
||||
@@ -102,7 +100,7 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
|
||||
}
|
||||
|
||||
opts = append(opts, h.spanStartOptions...)
|
||||
if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
|
||||
if h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx)) {
|
||||
opts = append(opts, trace.WithNewRoot())
|
||||
// Linking incoming span context if any for public endpoint.
|
||||
if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
|
||||
@@ -186,30 +184,26 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
|
||||
statusCode := rww.StatusCode()
|
||||
bytesWritten := rww.BytesWritten()
|
||||
span.SetStatus(h.semconv.Status(statusCode))
|
||||
bytesRead := bw.BytesRead()
|
||||
span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{
|
||||
StatusCode: statusCode,
|
||||
ReadBytes: bw.BytesRead(),
|
||||
ReadBytes: bytesRead,
|
||||
ReadError: bw.Error(),
|
||||
WriteBytes: bytesWritten,
|
||||
WriteError: rww.Error(),
|
||||
})...)
|
||||
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
|
||||
|
||||
metricAttributes := semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: statusCode,
|
||||
AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
|
||||
}
|
||||
|
||||
h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{
|
||||
ServerName: h.server,
|
||||
ResponseSize: bytesWritten,
|
||||
MetricAttributes: metricAttributes,
|
||||
ServerName: h.server,
|
||||
ResponseSize: bytesWritten,
|
||||
MetricAttributes: semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: statusCode,
|
||||
AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
|
||||
},
|
||||
MetricData: semconv.MetricData{
|
||||
RequestSize: bw.BytesRead(),
|
||||
ElapsedTime: elapsedTime,
|
||||
RequestSize: bytesRead,
|
||||
RequestDuration: time.Since(requestStartTime),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -221,18 +215,3 @@ func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.Ke
|
||||
}
|
||||
return attributeForRequest
|
||||
}
|
||||
|
||||
// WithRouteTag annotates spans and metrics with the provided route name
|
||||
// with HTTP route attribute.
|
||||
func WithRouteTag(route string, h http.Handler) http.Handler {
|
||||
attr := semconv.NewHTTPServer(nil).Route(route)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span := trace.SpanFromContext(r.Context())
|
||||
span.SetAttributes(attr)
|
||||
|
||||
labeler, _ := LabelerFromContext(r.Context())
|
||||
labeler.Add(attr)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
Generated
Vendored
+291
@@ -0,0 +1,291 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/client.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package semconv provides OpenTelemetry semantic convention types and
|
||||
// functionality.
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.39.0/httpconv"
|
||||
)
|
||||
|
||||
type HTTPClient struct {
|
||||
requestBodySize httpconv.ClientRequestBodySize
|
||||
requestDuration httpconv.ClientRequestDuration
|
||||
}
|
||||
|
||||
func NewHTTPClient(meter metric.Meter) HTTPClient {
|
||||
client := HTTPClient{}
|
||||
|
||||
var err error
|
||||
client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter)
|
||||
handleErr(err)
|
||||
|
||||
client.requestDuration, err = httpconv.NewClientRequestDuration(
|
||||
meter,
|
||||
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (n HTTPClient) Status(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 400 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
|
||||
func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
|
||||
/*
|
||||
below attributes are returned:
|
||||
- http.request.method
|
||||
- http.request.method.original
|
||||
- url.full
|
||||
- server.address
|
||||
- server.port
|
||||
- network.protocol.name
|
||||
- network.protocol.version
|
||||
*/
|
||||
numOfAttributes := 3 // URL, server address, proto, and method.
|
||||
|
||||
var urlHost string
|
||||
if req.URL != nil {
|
||||
urlHost = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if eligiblePort > 0 {
|
||||
numOfAttributes++
|
||||
}
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
numOfAttributes++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
method, originalMethod := n.method(req.Method)
|
||||
if originalMethod != (attribute.KeyValue{}) {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
|
||||
|
||||
attrs = append(attrs, method)
|
||||
if originalMethod != (attribute.KeyValue{}) {
|
||||
attrs = append(attrs, originalMethod)
|
||||
}
|
||||
|
||||
var u string
|
||||
if req.URL != nil {
|
||||
// Remove any username/password info that may be in the URL.
|
||||
userinfo := req.URL.User
|
||||
req.URL.User = nil
|
||||
u = req.URL.String()
|
||||
// Restore any username/password info that was removed.
|
||||
req.URL.User = userinfo
|
||||
}
|
||||
attrs = append(attrs, semconv.URLFull(u))
|
||||
|
||||
attrs = append(attrs, semconv.ServerAddress(requestHost))
|
||||
if eligiblePort > 0 {
|
||||
attrs = append(attrs, semconv.ServerPort(eligiblePort))
|
||||
}
|
||||
|
||||
if protoName != "" && protoName != "http" {
|
||||
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
|
||||
func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
|
||||
/*
|
||||
below attributes are returned:
|
||||
- http.response.status_code
|
||||
- error.type
|
||||
*/
|
||||
var count int
|
||||
if resp.StatusCode > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
if isErrorStatusCode(resp.StatusCode) {
|
||||
count++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, count)
|
||||
if resp.StatusCode > 0 {
|
||||
attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode))
|
||||
}
|
||||
|
||||
if isErrorStatusCode(resp.StatusCode) {
|
||||
errorType := strconv.Itoa(resp.StatusCode)
|
||||
attrs = append(attrs, semconv.ErrorTypeKey.String(errorType))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
}
|
||||
|
||||
orig := semconv.HTTPRequestMethodOriginal(method)
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconv.HTTPRequestMethodGet, orig
|
||||
}
|
||||
|
||||
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
num := len(additionalAttributes) + 2
|
||||
var h string
|
||||
if req.URL != nil {
|
||||
h = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{h, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if port > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
num++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, num)
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
||||
semconv.ServerAddress(requestHost),
|
||||
n.scheme(req),
|
||||
)
|
||||
|
||||
if port > 0 {
|
||||
attributes = append(attributes, semconv.ServerPort(port))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
type MetricOpts struct {
|
||||
measurement metric.MeasurementOption
|
||||
addOptions metric.AddOption
|
||||
}
|
||||
|
||||
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
|
||||
return o.measurement
|
||||
}
|
||||
|
||||
func (o MetricOpts) AddOptions() metric.AddOption {
|
||||
return o.addOptions
|
||||
}
|
||||
|
||||
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
||||
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
|
||||
return MetricOpts{
|
||||
measurement: set,
|
||||
addOptions: set,
|
||||
}
|
||||
}
|
||||
|
||||
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
|
||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||
defer func() {
|
||||
*recordOpts = (*recordOpts)[:0]
|
||||
metricRecordOptionPool.Put(recordOpts)
|
||||
}()
|
||||
*recordOpts = append(*recordOpts, opts.MeasurementOption())
|
||||
|
||||
n.requestBodySize.Inst().Record(ctx, md.RequestSize, *recordOpts...)
|
||||
n.requestDuration.Inst().Record(ctx, durationToSeconds(md.RequestDuration), *recordOpts...)
|
||||
}
|
||||
|
||||
// TraceAttributes returns attributes for httptrace.
|
||||
func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
|
||||
return []attribute.KeyValue{
|
||||
semconv.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue {
|
||||
if req.URL != nil && req.URL.Scheme != "" {
|
||||
return semconv.URLScheme(req.URL.Scheme)
|
||||
}
|
||||
if req.TLS != nil {
|
||||
return semconv.URLScheme("https")
|
||||
}
|
||||
return semconv.URLScheme("http")
|
||||
}
|
||||
|
||||
func isErrorStatusCode(code int) bool {
|
||||
return code >= 400 || code < 100
|
||||
}
|
||||
Generated
Vendored
-323
@@ -1,323 +0,0 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/env.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
)
|
||||
|
||||
// OTelSemConvStabilityOptIn is an environment variable.
|
||||
// That can be set to "http/dup" to keep getting the old HTTP semantic conventions.
|
||||
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"
|
||||
|
||||
type ResponseTelemetry struct {
|
||||
StatusCode int
|
||||
ReadBytes int64
|
||||
ReadError error
|
||||
WriteBytes int64
|
||||
WriteError error
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
duplicate bool
|
||||
|
||||
// Old metrics
|
||||
requestBytesCounter metric.Int64Counter
|
||||
responseBytesCounter metric.Int64Counter
|
||||
serverLatencyMeasure metric.Float64Histogram
|
||||
|
||||
// New metrics
|
||||
requestBodySizeHistogram metric.Int64Histogram
|
||||
responseBodySizeHistogram metric.Int64Histogram
|
||||
requestDurationHistogram metric.Float64Histogram
|
||||
}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
|
||||
// server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
|
||||
attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)
|
||||
if s.duplicate {
|
||||
return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
|
||||
if s.duplicate {
|
||||
return []attribute.KeyValue{
|
||||
OldHTTPServer{}.NetworkTransportAttr(network),
|
||||
CurrentHTTPServer{}.NetworkTransportAttr(network),
|
||||
}
|
||||
}
|
||||
return []attribute.KeyValue{
|
||||
CurrentHTTPServer{}.NetworkTransportAttr(network),
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
|
||||
//
|
||||
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
|
||||
func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
|
||||
attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp)
|
||||
if s.duplicate {
|
||||
return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
// Route returns the attribute for the route.
|
||||
func (s HTTPServer) Route(route string) attribute.KeyValue {
|
||||
return CurrentHTTPServer{}.Route(route)
|
||||
}
|
||||
|
||||
// Status returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func (s HTTPServer) Status(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 500 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
type ServerMetricData struct {
|
||||
ServerName string
|
||||
ResponseSize int64
|
||||
|
||||
MetricData
|
||||
MetricAttributes
|
||||
}
|
||||
|
||||
type MetricAttributes struct {
|
||||
Req *http.Request
|
||||
StatusCode int
|
||||
AdditionalAttributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
type MetricData struct {
|
||||
RequestSize int64
|
||||
|
||||
// The request duration, in milliseconds
|
||||
ElapsedTime float64
|
||||
}
|
||||
|
||||
var (
|
||||
metricAddOptionPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &[]metric.AddOption{}
|
||||
},
|
||||
}
|
||||
|
||||
metricRecordOptionPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
if s.requestDurationHistogram != nil && s.requestBodySizeHistogram != nil && s.responseBodySizeHistogram != nil {
|
||||
attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
|
||||
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||
*recordOpts = append(*recordOpts, o)
|
||||
s.requestBodySizeHistogram.Record(ctx, md.RequestSize, *recordOpts...)
|
||||
s.responseBodySizeHistogram.Record(ctx, md.ResponseSize, *recordOpts...)
|
||||
s.requestDurationHistogram.Record(ctx, md.ElapsedTime/1000.0, o)
|
||||
*recordOpts = (*recordOpts)[:0]
|
||||
metricRecordOptionPool.Put(recordOpts)
|
||||
}
|
||||
|
||||
if s.duplicate && s.requestBytesCounter != nil && s.responseBytesCounter != nil && s.serverLatencyMeasure != nil {
|
||||
attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
|
||||
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
addOpts := metricAddOptionPool.Get().(*[]metric.AddOption)
|
||||
*addOpts = append(*addOpts, o)
|
||||
s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...)
|
||||
s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...)
|
||||
s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o)
|
||||
*addOpts = (*addOpts)[:0]
|
||||
metricAddOptionPool.Put(addOpts)
|
||||
}
|
||||
}
|
||||
|
||||
// hasOptIn returns true if the comma-separated version string contains the
|
||||
// exact optIn value.
|
||||
func hasOptIn(version, optIn string) bool {
|
||||
for _, v := range strings.Split(version, ",") {
|
||||
if strings.TrimSpace(v) == optIn {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewHTTPServer(meter metric.Meter) HTTPServer {
|
||||
env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
|
||||
duplicate := hasOptIn(env, "http/dup")
|
||||
server := HTTPServer{
|
||||
duplicate: duplicate,
|
||||
}
|
||||
server.requestBodySizeHistogram, server.responseBodySizeHistogram, server.requestDurationHistogram = CurrentHTTPServer{}.createMeasures(meter)
|
||||
if duplicate {
|
||||
server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter)
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
type HTTPClient struct {
|
||||
duplicate bool
|
||||
|
||||
// old metrics
|
||||
requestBytesCounter metric.Int64Counter
|
||||
responseBytesCounter metric.Int64Counter
|
||||
latencyMeasure metric.Float64Histogram
|
||||
|
||||
// new metrics
|
||||
requestBodySize metric.Int64Histogram
|
||||
requestDuration metric.Float64Histogram
|
||||
}
|
||||
|
||||
func NewHTTPClient(meter metric.Meter) HTTPClient {
|
||||
env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
|
||||
duplicate := hasOptIn(env, "http/dup")
|
||||
client := HTTPClient{
|
||||
duplicate: duplicate,
|
||||
}
|
||||
client.requestBodySize, client.requestDuration = CurrentHTTPClient{}.createMeasures(meter)
|
||||
if duplicate {
|
||||
client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter)
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// RequestTraceAttrs returns attributes for an HTTP request made by a client.
|
||||
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
|
||||
attrs := CurrentHTTPClient{}.RequestTraceAttrs(req)
|
||||
if c.duplicate {
|
||||
return OldHTTPClient{}.RequestTraceAttrs(req, attrs)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
|
||||
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
|
||||
attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp)
|
||||
if c.duplicate {
|
||||
return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (c HTTPClient) Status(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 400 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||
return CurrentHTTPClient{}.ErrorType(err)
|
||||
}
|
||||
|
||||
type MetricOpts struct {
|
||||
measurement metric.MeasurementOption
|
||||
addOptions metric.AddOption
|
||||
}
|
||||
|
||||
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
|
||||
return o.measurement
|
||||
}
|
||||
|
||||
func (o MetricOpts) AddOptions() metric.AddOption {
|
||||
return o.addOptions
|
||||
}
|
||||
|
||||
func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
|
||||
opts := map[string]MetricOpts{}
|
||||
|
||||
attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
opts["new"] = MetricOpts{
|
||||
measurement: set,
|
||||
addOptions: set,
|
||||
}
|
||||
|
||||
if c.duplicate {
|
||||
attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
opts["old"] = MetricOpts{
|
||||
measurement: set,
|
||||
addOptions: set,
|
||||
}
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
|
||||
if s.requestBodySize == nil || s.requestDuration == nil {
|
||||
// This will happen if an HTTPClient{} is used instead of NewHTTPClient().
|
||||
return
|
||||
}
|
||||
|
||||
s.requestBodySize.Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
|
||||
s.requestDuration.Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())
|
||||
|
||||
if s.duplicate {
|
||||
s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions())
|
||||
s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption())
|
||||
}
|
||||
}
|
||||
|
||||
func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) {
|
||||
if s.responseBytesCounter == nil {
|
||||
// This will happen if an HTTPClient{} is used instead of NewHTTPClient().
|
||||
return
|
||||
}
|
||||
|
||||
s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions())
|
||||
}
|
||||
|
||||
func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
|
||||
attrs := CurrentHTTPClient{}.TraceAttributes(host)
|
||||
if s.duplicate {
|
||||
return OldHTTPClient{}.TraceAttributes(host, attrs)
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
Generated
Vendored
+6
-5
@@ -5,10 +5,11 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/
|
||||
|
||||
// Generate semconv package:
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=bench_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=common_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconvtest_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=v1.20.0.go
|
||||
|
||||
Generated
Vendored
-573
@@ -1,573 +0,0 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/httpconv.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package semconv provides OpenTelemetry semantic convention types and
|
||||
// functionality.
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/metric/noop"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
)
|
||||
|
||||
type RequestTraceAttrsOpts struct {
|
||||
// If set, this is used as value for the "http.client_ip" attribute.
|
||||
HTTPClientIP string
|
||||
}
|
||||
|
||||
type CurrentHTTPServer struct{}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
|
||||
// server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
|
||||
count := 3 // ServerAddress, Method, Scheme
|
||||
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = SplitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = SplitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = SplitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
method, methodOriginal := n.method(req.Method)
|
||||
if methodOriginal != (attribute.KeyValue{}) {
|
||||
count++
|
||||
}
|
||||
|
||||
scheme := n.scheme(req.TLS != nil)
|
||||
|
||||
peer, peerPort := SplitHostPort(req.RemoteAddr)
|
||||
if peer != "" {
|
||||
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
|
||||
// file-path that would be interpreted with a sock family.
|
||||
count++
|
||||
if peerPort > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
// For client IP, use, in order:
|
||||
// 1. The value passed in the options
|
||||
// 2. The value in the X-Forwarded-For header
|
||||
// 3. The peer address
|
||||
clientIP := opts.HTTPClientIP
|
||||
if clientIP == "" {
|
||||
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
|
||||
if clientIP == "" {
|
||||
clientIP = peer
|
||||
}
|
||||
}
|
||||
if clientIP != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
if req.URL != nil && req.URL.Path != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
count++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
route := httpRoute(req.Pattern)
|
||||
if route != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, count)
|
||||
attrs = append(attrs,
|
||||
semconvNew.ServerAddress(host),
|
||||
method,
|
||||
scheme,
|
||||
)
|
||||
|
||||
if hostPort > 0 {
|
||||
attrs = append(attrs, semconvNew.ServerPort(hostPort))
|
||||
}
|
||||
if methodOriginal != (attribute.KeyValue{}) {
|
||||
attrs = append(attrs, methodOriginal)
|
||||
}
|
||||
|
||||
if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
|
||||
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
|
||||
// file-path that would be interpreted with a sock family.
|
||||
attrs = append(attrs, semconvNew.NetworkPeerAddress(peer))
|
||||
if peerPort > 0 {
|
||||
attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort))
|
||||
}
|
||||
}
|
||||
|
||||
if useragent != "" {
|
||||
attrs = append(attrs, semconvNew.UserAgentOriginal(useragent))
|
||||
}
|
||||
|
||||
if clientIP != "" {
|
||||
attrs = append(attrs, semconvNew.ClientAddress(clientIP))
|
||||
}
|
||||
|
||||
if req.URL != nil && req.URL.Path != "" {
|
||||
attrs = append(attrs, semconvNew.URLPath(req.URL.Path))
|
||||
}
|
||||
|
||||
if protoName != "" && protoName != "http" {
|
||||
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if route != "" {
|
||||
attrs = append(attrs, n.Route(route))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
return semconvNew.NetworkTransportTCP
|
||||
case "udp", "udp4", "udp6":
|
||||
return semconvNew.NetworkTransportUDP
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
return semconvNew.NetworkTransportUnix
|
||||
default:
|
||||
return semconvNew.NetworkTransportPipe
|
||||
}
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
}
|
||||
|
||||
orig := semconvNew.HTTPRequestMethodOriginal(method)
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconvNew.HTTPRequestMethodGet, orig
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
|
||||
if https {
|
||||
return semconvNew.URLScheme("https")
|
||||
}
|
||||
return semconvNew.URLScheme("http")
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
|
||||
// response.
|
||||
//
|
||||
// If any of the fields in the ResponseTelemetry are not set the attribute will
|
||||
// be omitted.
|
||||
func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
|
||||
var count int
|
||||
|
||||
if resp.ReadBytes > 0 {
|
||||
count++
|
||||
}
|
||||
if resp.WriteBytes > 0 {
|
||||
count++
|
||||
}
|
||||
if resp.StatusCode > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
attributes := make([]attribute.KeyValue, 0, count)
|
||||
|
||||
if resp.ReadBytes > 0 {
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)),
|
||||
)
|
||||
}
|
||||
if resp.WriteBytes > 0 {
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)),
|
||||
)
|
||||
}
|
||||
if resp.StatusCode > 0 {
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPResponseStatusCode(resp.StatusCode),
|
||||
)
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
// Route returns the attribute for the route.
|
||||
func (n CurrentHTTPServer) Route(route string) attribute.KeyValue {
|
||||
return semconvNew.HTTPRoute(route)
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Int64Histogram, metric.Float64Histogram) {
|
||||
if meter == nil {
|
||||
return noop.Int64Histogram{}, noop.Int64Histogram{}, noop.Float64Histogram{}
|
||||
}
|
||||
|
||||
var err error
|
||||
requestBodySizeHistogram, err := meter.Int64Histogram(
|
||||
semconvNew.HTTPServerRequestBodySizeName,
|
||||
metric.WithUnit(semconvNew.HTTPServerRequestBodySizeUnit),
|
||||
metric.WithDescription(semconvNew.HTTPServerRequestBodySizeDescription),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
responseBodySizeHistogram, err := meter.Int64Histogram(
|
||||
semconvNew.HTTPServerResponseBodySizeName,
|
||||
metric.WithUnit(semconvNew.HTTPServerResponseBodySizeUnit),
|
||||
metric.WithDescription(semconvNew.HTTPServerResponseBodySizeDescription),
|
||||
)
|
||||
handleErr(err)
|
||||
requestDurationHistogram, err := meter.Float64Histogram(
|
||||
semconvNew.HTTPServerRequestDurationName,
|
||||
metric.WithUnit(semconvNew.HTTPServerRequestDurationUnit),
|
||||
metric.WithDescription(semconvNew.HTTPServerRequestDurationDescription),
|
||||
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
return requestBodySizeHistogram, responseBodySizeHistogram, requestDurationHistogram
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
num := len(additionalAttributes) + 3
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = SplitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = SplitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = SplitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
num++
|
||||
}
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
num++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, num)
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
||||
n.scheme(req.TLS != nil),
|
||||
semconvNew.ServerAddress(host))
|
||||
|
||||
if hostPort > 0 {
|
||||
attributes = append(attributes, semconvNew.ServerPort(hostPort))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
type CurrentHTTPClient struct{}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
|
||||
func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
|
||||
/*
|
||||
below attributes are returned:
|
||||
- http.request.method
|
||||
- http.request.method.original
|
||||
- url.full
|
||||
- server.address
|
||||
- server.port
|
||||
- network.protocol.name
|
||||
- network.protocol.version
|
||||
*/
|
||||
numOfAttributes := 3 // URL, server address, proto, and method.
|
||||
|
||||
var urlHost string
|
||||
if req.URL != nil {
|
||||
urlHost = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if eligiblePort > 0 {
|
||||
numOfAttributes++
|
||||
}
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
numOfAttributes++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
method, originalMethod := n.method(req.Method)
|
||||
if originalMethod != (attribute.KeyValue{}) {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
|
||||
|
||||
attrs = append(attrs, method)
|
||||
if originalMethod != (attribute.KeyValue{}) {
|
||||
attrs = append(attrs, originalMethod)
|
||||
}
|
||||
|
||||
var u string
|
||||
if req.URL != nil {
|
||||
// Remove any username/password info that may be in the URL.
|
||||
userinfo := req.URL.User
|
||||
req.URL.User = nil
|
||||
u = req.URL.String()
|
||||
// Restore any username/password info that was removed.
|
||||
req.URL.User = userinfo
|
||||
}
|
||||
attrs = append(attrs, semconvNew.URLFull(u))
|
||||
|
||||
attrs = append(attrs, semconvNew.ServerAddress(requestHost))
|
||||
if eligiblePort > 0 {
|
||||
attrs = append(attrs, semconvNew.ServerPort(eligiblePort))
|
||||
}
|
||||
|
||||
if protoName != "" && protoName != "http" {
|
||||
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
|
||||
func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
|
||||
/*
|
||||
below attributes are returned:
|
||||
- http.response.status_code
|
||||
- error.type
|
||||
*/
|
||||
var count int
|
||||
if resp.StatusCode > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
if isErrorStatusCode(resp.StatusCode) {
|
||||
count++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, count)
|
||||
if resp.StatusCode > 0 {
|
||||
attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode))
|
||||
}
|
||||
|
||||
if isErrorStatusCode(resp.StatusCode) {
|
||||
errorType := strconv.Itoa(resp.StatusCode)
|
||||
attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||
t := reflect.TypeOf(err)
|
||||
var value string
|
||||
if t.PkgPath() == "" && t.Name() == "" {
|
||||
// Likely a builtin type.
|
||||
value = t.String()
|
||||
} else {
|
||||
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
return semconvNew.ErrorTypeOther
|
||||
}
|
||||
|
||||
return semconvNew.ErrorTypeKey.String(value)
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
}
|
||||
|
||||
orig := semconvNew.HTTPRequestMethodOriginal(method)
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconvNew.HTTPRequestMethodGet, orig
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Float64Histogram) {
|
||||
if meter == nil {
|
||||
return noop.Int64Histogram{}, noop.Float64Histogram{}
|
||||
}
|
||||
|
||||
var err error
|
||||
requestBodySize, err := meter.Int64Histogram(
|
||||
semconvNew.HTTPClientRequestBodySizeName,
|
||||
metric.WithUnit(semconvNew.HTTPClientRequestBodySizeUnit),
|
||||
metric.WithDescription(semconvNew.HTTPClientRequestBodySizeDescription),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
requestDuration, err := meter.Float64Histogram(
|
||||
semconvNew.HTTPClientRequestDurationName,
|
||||
metric.WithUnit(semconvNew.HTTPClientRequestDurationUnit),
|
||||
metric.WithDescription(semconvNew.HTTPClientRequestDurationDescription),
|
||||
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
return requestBodySize, requestDuration
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
num := len(additionalAttributes) + 2
|
||||
var h string
|
||||
if req.URL != nil {
|
||||
h = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{h, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if port > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
num++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, num)
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
||||
semconvNew.ServerAddress(requestHost),
|
||||
n.scheme(req),
|
||||
)
|
||||
|
||||
if port > 0 {
|
||||
attributes = append(attributes, semconvNew.ServerPort(port))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
// TraceAttributes returns attributes for httptrace.
|
||||
func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue {
|
||||
return []attribute.KeyValue{
|
||||
semconvNew.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue {
|
||||
if req.URL != nil && req.URL.Scheme != "" {
|
||||
return semconvNew.URLScheme(req.URL.Scheme)
|
||||
}
|
||||
if req.TLS != nil {
|
||||
return semconvNew.URLScheme("https")
|
||||
}
|
||||
return semconvNew.URLScheme("http")
|
||||
}
|
||||
|
||||
func isErrorStatusCode(code int) bool {
|
||||
return code >= 400 || code < 100
|
||||
}
|
||||
Generated
Vendored
+396
@@ -0,0 +1,396 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/server.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package semconv provides OpenTelemetry semantic convention types and
|
||||
// functionality.
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.39.0/httpconv"
|
||||
)
|
||||
|
||||
type RequestTraceAttrsOpts struct {
|
||||
// If set, this is used as value for the "http.client_ip" attribute.
|
||||
HTTPClientIP string
|
||||
}
|
||||
|
||||
type ResponseTelemetry struct {
|
||||
StatusCode int
|
||||
ReadBytes int64
|
||||
ReadError error
|
||||
WriteBytes int64
|
||||
WriteError error
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
requestBodySizeHistogram httpconv.ServerRequestBodySize
|
||||
responseBodySizeHistogram httpconv.ServerResponseBodySize
|
||||
requestDurationHistogram httpconv.ServerRequestDuration
|
||||
}
|
||||
|
||||
func NewHTTPServer(meter metric.Meter) HTTPServer {
|
||||
server := HTTPServer{}
|
||||
|
||||
var err error
|
||||
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
|
||||
handleErr(err)
|
||||
|
||||
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
|
||||
handleErr(err)
|
||||
|
||||
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
|
||||
meter,
|
||||
metric.WithExplicitBucketBoundaries(
|
||||
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
|
||||
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
|
||||
),
|
||||
)
|
||||
handleErr(err)
|
||||
return server
|
||||
}
|
||||
|
||||
// Status returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func (n HTTPServer) Status(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 500 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
|
||||
// server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
|
||||
count := 3 // ServerAddress, Method, Scheme
|
||||
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = SplitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = SplitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = SplitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
method, methodOriginal := n.method(req.Method)
|
||||
if methodOriginal != (attribute.KeyValue{}) {
|
||||
count++
|
||||
}
|
||||
|
||||
scheme := n.scheme(req.TLS != nil)
|
||||
|
||||
peer, peerPort := SplitHostPort(req.RemoteAddr)
|
||||
if peer != "" {
|
||||
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
|
||||
// file-path that would be interpreted with a sock family.
|
||||
count++
|
||||
if peerPort > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
// For client IP, use, in order:
|
||||
// 1. The value passed in the options
|
||||
// 2. The value in the X-Forwarded-For header
|
||||
// 3. The peer address
|
||||
clientIP := opts.HTTPClientIP
|
||||
if clientIP == "" {
|
||||
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
|
||||
if clientIP == "" {
|
||||
clientIP = peer
|
||||
}
|
||||
}
|
||||
if clientIP != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
if req.URL != nil && req.URL.Path != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
count++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
route := httpRoute(req.Pattern)
|
||||
if route != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, count)
|
||||
attrs = append(attrs,
|
||||
semconv.ServerAddress(host),
|
||||
method,
|
||||
scheme,
|
||||
)
|
||||
|
||||
if hostPort > 0 {
|
||||
attrs = append(attrs, semconv.ServerPort(hostPort))
|
||||
}
|
||||
if methodOriginal != (attribute.KeyValue{}) {
|
||||
attrs = append(attrs, methodOriginal)
|
||||
}
|
||||
|
||||
if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
|
||||
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
|
||||
// file-path that would be interpreted with a sock family.
|
||||
attrs = append(attrs, semconv.NetworkPeerAddress(peer))
|
||||
if peerPort > 0 {
|
||||
attrs = append(attrs, semconv.NetworkPeerPort(peerPort))
|
||||
}
|
||||
}
|
||||
|
||||
if useragent != "" {
|
||||
attrs = append(attrs, semconv.UserAgentOriginal(useragent))
|
||||
}
|
||||
|
||||
if clientIP != "" {
|
||||
attrs = append(attrs, semconv.ClientAddress(clientIP))
|
||||
}
|
||||
|
||||
if req.URL != nil && req.URL.Path != "" {
|
||||
attrs = append(attrs, semconv.URLPath(req.URL.Path))
|
||||
}
|
||||
|
||||
if protoName != "" && protoName != "http" {
|
||||
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if route != "" {
|
||||
attrs = append(attrs, n.Route(route))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
|
||||
attr := semconv.NetworkTransportPipe
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
attr = semconv.NetworkTransportTCP
|
||||
case "udp", "udp4", "udp6":
|
||||
attr = semconv.NetworkTransportUDP
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
attr = semconv.NetworkTransportUnix
|
||||
}
|
||||
|
||||
return []attribute.KeyValue{attr}
|
||||
}
|
||||
|
||||
type ServerMetricData struct {
|
||||
ServerName string
|
||||
ResponseSize int64
|
||||
|
||||
MetricData
|
||||
MetricAttributes
|
||||
}
|
||||
|
||||
type MetricAttributes struct {
|
||||
Req *http.Request
|
||||
StatusCode int
|
||||
Route string
|
||||
AdditionalAttributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
type MetricData struct {
|
||||
RequestSize int64
|
||||
RequestDuration time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
metricRecordOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
|
||||
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||
*recordOpts = append(*recordOpts, o)
|
||||
n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
|
||||
n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
|
||||
n.requestDurationHistogram.Inst().Record(ctx, durationToSeconds(md.RequestDuration), o)
|
||||
*recordOpts = (*recordOpts)[:0]
|
||||
metricRecordOptionPool.Put(recordOpts)
|
||||
}
|
||||
|
||||
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
}
|
||||
|
||||
orig := semconv.HTTPRequestMethodOriginal(method)
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconv.HTTPRequestMethodGet, orig
|
||||
}
|
||||
|
||||
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
|
||||
if https {
|
||||
return semconv.URLScheme("https")
|
||||
}
|
||||
return semconv.URLScheme("http")
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
|
||||
// response.
|
||||
//
|
||||
// If any of the fields in the ResponseTelemetry are not set the attribute will
|
||||
// be omitted.
|
||||
func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
|
||||
var count int
|
||||
|
||||
if resp.ReadBytes > 0 {
|
||||
count++
|
||||
}
|
||||
if resp.WriteBytes > 0 {
|
||||
count++
|
||||
}
|
||||
if resp.StatusCode > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
attributes := make([]attribute.KeyValue, 0, count)
|
||||
|
||||
if resp.ReadBytes > 0 {
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPRequestBodySize(int(resp.ReadBytes)),
|
||||
)
|
||||
}
|
||||
if resp.WriteBytes > 0 {
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPResponseBodySize(int(resp.WriteBytes)),
|
||||
)
|
||||
}
|
||||
if resp.StatusCode > 0 {
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPResponseStatusCode(resp.StatusCode),
|
||||
)
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
// Route returns the attribute for the route.
|
||||
func (n HTTPServer) Route(route string) attribute.KeyValue {
|
||||
return semconv.HTTPRoute(route)
|
||||
}
|
||||
|
||||
func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
num := len(additionalAttributes) + 3
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = SplitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = SplitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = SplitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
num++
|
||||
}
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
num++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
if route != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, num)
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
||||
n.scheme(req.TLS != nil),
|
||||
semconv.ServerAddress(host))
|
||||
|
||||
if hostPort > 0 {
|
||||
attributes = append(attributes, semconv.ServerPort(hostPort))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
|
||||
}
|
||||
|
||||
if route != "" {
|
||||
attributes = append(attributes, semconv.HTTPRoute(route))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
Generated
Vendored
+9
-3
@@ -11,10 +11,11 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
)
|
||||
|
||||
// SplitHostPort splits a network address hostport of the form "host",
|
||||
@@ -53,10 +54,10 @@ func SplitHostPort(hostport string) (host string, port int) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return host, int(p) // nolint: gosec // Byte size checked 16 above.
|
||||
return host, int(p) //nolint:gosec // Byte size checked 16 above.
|
||||
}
|
||||
|
||||
func requiredHTTPPort(https bool, port int) int { // nolint:revive
|
||||
func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter
|
||||
if https {
|
||||
if port > 0 && port != 443 {
|
||||
return port
|
||||
@@ -125,3 +126,8 @@ func standardizeHTTPMethod(method string) string {
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func durationToSeconds(d time.Duration) float64 {
|
||||
// Use floating point division here for higher precision (instead of Seconds method).
|
||||
return float64(d) / float64(time.Second)
|
||||
}
|
||||
|
||||
Generated
Vendored
-273
@@ -1,273 +0,0 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/v120.0.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/metric/noop"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
|
||||
)
|
||||
|
||||
type OldHTTPServer struct{}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
|
||||
// server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs)
|
||||
}
|
||||
|
||||
func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
|
||||
return semconvutil.NetTransport(network)
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
|
||||
//
|
||||
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
|
||||
func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
if resp.ReadBytes > 0 {
|
||||
attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes)))
|
||||
}
|
||||
if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) {
|
||||
// This is not in the semantic conventions, but is historically provided
|
||||
attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error()))
|
||||
}
|
||||
if resp.WriteBytes > 0 {
|
||||
attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes)))
|
||||
}
|
||||
if resp.StatusCode > 0 {
|
||||
attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode))
|
||||
}
|
||||
if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) {
|
||||
// This is not in the semantic conventions, but is historically provided
|
||||
attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error()))
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
// Route returns the attribute for the route.
|
||||
func (o OldHTTPServer) Route(route string) attribute.KeyValue {
|
||||
return semconv.HTTPRoute(route)
|
||||
}
|
||||
|
||||
// HTTPStatusCode returns the attribute for the HTTP status code.
|
||||
// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added.
|
||||
func HTTPStatusCode(status int) attribute.KeyValue {
|
||||
return semconv.HTTPStatusCode(status)
|
||||
}
|
||||
|
||||
// Server HTTP metrics.
|
||||
const (
|
||||
serverRequestSize = "http.server.request.size" // Incoming request bytes total
|
||||
serverResponseSize = "http.server.response.size" // Incoming response bytes total
|
||||
serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds
|
||||
)
|
||||
|
||||
func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
|
||||
if meter == nil {
|
||||
return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
|
||||
}
|
||||
var err error
|
||||
requestBytesCounter, err := meter.Int64Counter(
|
||||
serverRequestSize,
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Measures the size of HTTP request messages."),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
responseBytesCounter, err := meter.Int64Counter(
|
||||
serverResponseSize,
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Measures the size of HTTP response messages."),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
serverLatencyMeasure, err := meter.Float64Histogram(
|
||||
serverDuration,
|
||||
metric.WithUnit("ms"),
|
||||
metric.WithDescription("Measures the duration of inbound HTTP requests."),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
return requestBytesCounter, responseBytesCounter, serverLatencyMeasure
|
||||
}
|
||||
|
||||
func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
n := len(additionalAttributes) + 3
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = SplitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = SplitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = SplitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
n++
|
||||
}
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
n++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
n++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, n)
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPMethod(standardizeHTTPMethod(req.Method)),
|
||||
o.scheme(req.TLS != nil),
|
||||
semconv.NetHostName(host))
|
||||
|
||||
if hostPort > 0 {
|
||||
attributes = append(attributes, semconv.NetHostPort(hostPort))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconv.NetProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconv.NetProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
|
||||
if https {
|
||||
return semconv.HTTPSchemeHTTPS
|
||||
}
|
||||
return semconv.HTTPSchemeHTTP
|
||||
}
|
||||
|
||||
type OldHTTPClient struct{}
|
||||
|
||||
func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return semconvutil.HTTPClientRequest(req, attrs)
|
||||
}
|
||||
|
||||
func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return semconvutil.HTTPClientResponse(resp, attrs)
|
||||
}
|
||||
|
||||
func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
/* The following semantic conventions are returned if present:
|
||||
http.method string
|
||||
http.status_code int
|
||||
net.peer.name string
|
||||
net.peer.port int
|
||||
*/
|
||||
|
||||
n := 2 // method, peer name.
|
||||
var h string
|
||||
if req.URL != nil {
|
||||
h = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{h, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if port > 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, n)
|
||||
attributes = append(attributes,
|
||||
semconv.HTTPMethod(standardizeHTTPMethod(req.Method)),
|
||||
semconv.NetPeerName(requestHost),
|
||||
)
|
||||
|
||||
if port > 0 {
|
||||
attributes = append(attributes, semconv.NetPeerPort(port))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
// Client HTTP metrics.
|
||||
const (
|
||||
clientRequestSize = "http.client.request.size" // Incoming request bytes total
|
||||
clientResponseSize = "http.client.response.size" // Incoming response bytes total
|
||||
clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds
|
||||
)
|
||||
|
||||
func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
|
||||
if meter == nil {
|
||||
return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
|
||||
}
|
||||
requestBytesCounter, err := meter.Int64Counter(
|
||||
clientRequestSize,
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Measures the size of HTTP request messages."),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
responseBytesCounter, err := meter.Int64Counter(
|
||||
clientResponseSize,
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Measures the size of HTTP response messages."),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
latencyMeasure, err := meter.Float64Histogram(
|
||||
clientDuration,
|
||||
metric.WithUnit("ms"),
|
||||
metric.WithDescription("Measures the duration of outbound HTTP requests."),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
return requestBytesCounter, responseBytesCounter, latencyMeasure
|
||||
}
|
||||
|
||||
// TraceAttributes returns attributes for httptrace.
|
||||
func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return append(attrs, semconv.NetHostName(host))
|
||||
}
|
||||
Generated
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
|
||||
|
||||
// Generate semconvutil package:
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go
|
||||
Generated
Vendored
-594
@@ -1,594 +0,0 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconvutil/httpconv.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package semconvutil provides OpenTelemetry semantic convention utilities.
|
||||
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
|
||||
)
|
||||
|
||||
type HTTPServerRequestOptions struct {
|
||||
// If set, this is used as value for the "http.client_ip" attribute.
|
||||
HTTPClientIP string
|
||||
}
|
||||
|
||||
// HTTPClientResponse returns trace attributes for an HTTP response received by a
|
||||
// client from a server. It will return the following attributes if the related
|
||||
// values are defined in resp: "http.status.code",
|
||||
// "http.response_content_length".
|
||||
//
|
||||
// This does not add all OpenTelemetry required attributes for an HTTP event,
|
||||
// it assumes ClientRequest was used to create the span with a complete set of
|
||||
// attributes. If a complete set of attributes can be generated using the
|
||||
// request contained in resp. For example:
|
||||
//
|
||||
// HTTPClientResponse(resp, ClientRequest(resp.Request)))
|
||||
func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return hc.ClientResponse(resp, attrs)
|
||||
}
|
||||
|
||||
// HTTPClientRequest returns trace attributes for an HTTP request made by a client.
|
||||
// The following attributes are always returned: "http.url", "http.method",
|
||||
// "net.peer.name". The following attributes are returned if the related values
|
||||
// are defined in req: "net.peer.port", "user_agent.original",
|
||||
// "http.request_content_length".
|
||||
func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return hc.ClientRequest(req, attrs)
|
||||
}
|
||||
|
||||
// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client.
|
||||
// The following attributes are always returned: "http.method", "net.peer.name".
|
||||
// The following attributes are returned if the
|
||||
// related values are defined in req: "net.peer.port".
|
||||
func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue {
|
||||
return hc.ClientRequestMetrics(req)
|
||||
}
|
||||
|
||||
// HTTPClientStatus returns a span status code and message for an HTTP status code
|
||||
// value received by a client.
|
||||
func HTTPClientStatus(code int) (codes.Code, string) {
|
||||
return hc.ClientStatus(code)
|
||||
}
|
||||
|
||||
// HTTPServerRequest returns trace attributes for an HTTP request received by a
|
||||
// server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
//
|
||||
// The following attributes are always returned: "http.method", "http.scheme",
|
||||
// "http.target", "net.host.name". The following attributes are returned if
|
||||
// they related values are defined in req: "net.host.port", "net.sock.peer.addr",
|
||||
// "net.sock.peer.port", "user_agent.original", "http.client_ip".
|
||||
func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
return hc.ServerRequest(server, req, opts, attrs)
|
||||
}
|
||||
|
||||
// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a
|
||||
// server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
//
|
||||
// The following attributes are always returned: "http.method", "http.scheme",
|
||||
// "net.host.name". The following attributes are returned if they related
|
||||
// values are defined in req: "net.host.port".
|
||||
func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue {
|
||||
return hc.ServerRequestMetrics(server, req)
|
||||
}
|
||||
|
||||
// HTTPServerStatus returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func HTTPServerStatus(code int) (codes.Code, string) {
|
||||
return hc.ServerStatus(code)
|
||||
}
|
||||
|
||||
// httpConv are the HTTP semantic convention attributes defined for a version
|
||||
// of the OpenTelemetry specification.
|
||||
type httpConv struct {
|
||||
NetConv *netConv
|
||||
|
||||
HTTPClientIPKey attribute.Key
|
||||
HTTPMethodKey attribute.Key
|
||||
HTTPRequestContentLengthKey attribute.Key
|
||||
HTTPResponseContentLengthKey attribute.Key
|
||||
HTTPRouteKey attribute.Key
|
||||
HTTPSchemeHTTP attribute.KeyValue
|
||||
HTTPSchemeHTTPS attribute.KeyValue
|
||||
HTTPStatusCodeKey attribute.Key
|
||||
HTTPTargetKey attribute.Key
|
||||
HTTPURLKey attribute.Key
|
||||
UserAgentOriginalKey attribute.Key
|
||||
}
|
||||
|
||||
var hc = &httpConv{
|
||||
NetConv: nc,
|
||||
|
||||
HTTPClientIPKey: semconv.HTTPClientIPKey,
|
||||
HTTPMethodKey: semconv.HTTPMethodKey,
|
||||
HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey,
|
||||
HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey,
|
||||
HTTPRouteKey: semconv.HTTPRouteKey,
|
||||
HTTPSchemeHTTP: semconv.HTTPSchemeHTTP,
|
||||
HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS,
|
||||
HTTPStatusCodeKey: semconv.HTTPStatusCodeKey,
|
||||
HTTPTargetKey: semconv.HTTPTargetKey,
|
||||
HTTPURLKey: semconv.HTTPURLKey,
|
||||
UserAgentOriginalKey: semconv.UserAgentOriginalKey,
|
||||
}
|
||||
|
||||
// ClientResponse returns attributes for an HTTP response received by a client
|
||||
// from a server. The following attributes are returned if the related values
|
||||
// are defined in resp: "http.status.code", "http.response_content_length".
|
||||
//
|
||||
// This does not add all OpenTelemetry required attributes for an HTTP event,
|
||||
// it assumes ClientRequest was used to create the span with a complete set of
|
||||
// attributes. If a complete set of attributes can be generated using the
|
||||
// request contained in resp. For example:
|
||||
//
|
||||
// ClientResponse(resp, ClientRequest(resp.Request))
|
||||
func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
/* The following semantic conventions are returned if present:
|
||||
http.status_code int
|
||||
http.response_content_length int
|
||||
*/
|
||||
var n int
|
||||
if resp.StatusCode > 0 {
|
||||
n++
|
||||
}
|
||||
if resp.ContentLength > 0 {
|
||||
n++
|
||||
}
|
||||
if n == 0 {
|
||||
return attrs
|
||||
}
|
||||
|
||||
attrs = slices.Grow(attrs, n)
|
||||
if resp.StatusCode > 0 {
|
||||
attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode))
|
||||
}
|
||||
if resp.ContentLength > 0 {
|
||||
attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength)))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ClientRequest returns attributes for an HTTP request made by a client. The
|
||||
// following attributes are always returned: "http.url", "http.method",
|
||||
// "net.peer.name". The following attributes are returned if the related values
|
||||
// are defined in req: "net.peer.port", "user_agent.original",
|
||||
// "http.request_content_length", "user_agent.original".
|
||||
func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
/* The following semantic conventions are returned if present:
|
||||
http.method string
|
||||
user_agent.original string
|
||||
http.url string
|
||||
net.peer.name string
|
||||
net.peer.port int
|
||||
http.request_content_length int
|
||||
*/
|
||||
|
||||
/* The following semantic conventions are not returned:
|
||||
http.status_code This requires the response. See ClientResponse.
|
||||
http.response_content_length This requires the response. See ClientResponse.
|
||||
net.sock.family This requires the socket used.
|
||||
net.sock.peer.addr This requires the socket used.
|
||||
net.sock.peer.name This requires the socket used.
|
||||
net.sock.peer.port This requires the socket used.
|
||||
http.resend_count This is something outside of a single request.
|
||||
net.protocol.name The value is the Request is ignored, and the go client will always use "http".
|
||||
net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0.
|
||||
*/
|
||||
n := 3 // URL, peer name, proto, and method.
|
||||
var h string
|
||||
if req.URL != nil {
|
||||
h = req.URL.Host
|
||||
}
|
||||
peer, p := firstHostPort(h, req.Header.Get("Host"))
|
||||
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
|
||||
if port > 0 {
|
||||
n++
|
||||
}
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
n++
|
||||
}
|
||||
if req.ContentLength > 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
attrs = slices.Grow(attrs, n)
|
||||
attrs = append(attrs, c.method(req.Method))
|
||||
|
||||
var u string
|
||||
if req.URL != nil {
|
||||
// Remove any username/password info that may be in the URL.
|
||||
userinfo := req.URL.User
|
||||
req.URL.User = nil
|
||||
u = req.URL.String()
|
||||
// Restore any username/password info that was removed.
|
||||
req.URL.User = userinfo
|
||||
}
|
||||
attrs = append(attrs, c.HTTPURLKey.String(u))
|
||||
|
||||
attrs = append(attrs, c.NetConv.PeerName(peer))
|
||||
if port > 0 {
|
||||
attrs = append(attrs, c.NetConv.PeerPort(port))
|
||||
}
|
||||
|
||||
if useragent != "" {
|
||||
attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
|
||||
}
|
||||
|
||||
if l := req.ContentLength; l > 0 {
|
||||
attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The
|
||||
// following attributes are always returned: "http.method", "net.peer.name".
|
||||
// The following attributes are returned if the related values
|
||||
// are defined in req: "net.peer.port".
|
||||
func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue {
|
||||
/* The following semantic conventions are returned if present:
|
||||
http.method string
|
||||
net.peer.name string
|
||||
net.peer.port int
|
||||
*/
|
||||
|
||||
n := 2 // method, peer name.
|
||||
var h string
|
||||
if req.URL != nil {
|
||||
h = req.URL.Host
|
||||
}
|
||||
peer, p := firstHostPort(h, req.Header.Get("Host"))
|
||||
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
|
||||
if port > 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, n)
|
||||
attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer))
|
||||
|
||||
if port > 0 {
|
||||
attrs = append(attrs, c.NetConv.PeerPort(port))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ServerRequest returns attributes for an HTTP request received by a server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
//
|
||||
// The following attributes are always returned: "http.method", "http.scheme",
|
||||
// "http.target", "net.host.name". The following attributes are returned if they
|
||||
// related values are defined in req: "net.host.port", "net.sock.peer.addr",
|
||||
// "net.sock.peer.port", "user_agent.original", "http.client_ip",
|
||||
// "net.protocol.name", "net.protocol.version".
|
||||
func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue {
|
||||
/* The following semantic conventions are returned if present:
|
||||
http.method string
|
||||
http.scheme string
|
||||
net.host.name string
|
||||
net.host.port int
|
||||
net.sock.peer.addr string
|
||||
net.sock.peer.port int
|
||||
user_agent.original string
|
||||
http.client_ip string
|
||||
net.protocol.name string Note: not set if the value is "http".
|
||||
net.protocol.version string
|
||||
http.target string Note: doesn't include the query parameter.
|
||||
*/
|
||||
|
||||
/* The following semantic conventions are not returned:
|
||||
http.status_code This requires the response.
|
||||
http.request_content_length This requires the len() of body, which can mutate it.
|
||||
http.response_content_length This requires the response.
|
||||
http.route This is not available.
|
||||
net.sock.peer.name This would require a DNS lookup.
|
||||
net.sock.host.addr The request doesn't have access to the underlying socket.
|
||||
net.sock.host.port The request doesn't have access to the underlying socket.
|
||||
|
||||
*/
|
||||
n := 4 // Method, scheme, proto, and host name.
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = splitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = splitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = splitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
n++
|
||||
}
|
||||
peer, peerPort := splitHostPort(req.RemoteAddr)
|
||||
if peer != "" {
|
||||
n++
|
||||
if peerPort > 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
n++
|
||||
}
|
||||
|
||||
// For client IP, use, in order:
|
||||
// 1. The value passed in the options
|
||||
// 2. The value in the X-Forwarded-For header
|
||||
// 3. The peer address
|
||||
clientIP := opts.HTTPClientIP
|
||||
if clientIP == "" {
|
||||
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
|
||||
if clientIP == "" {
|
||||
clientIP = peer
|
||||
}
|
||||
}
|
||||
if clientIP != "" {
|
||||
n++
|
||||
}
|
||||
|
||||
var target string
|
||||
if req.URL != nil {
|
||||
target = req.URL.Path
|
||||
if target != "" {
|
||||
n++
|
||||
}
|
||||
}
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
n++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
n++
|
||||
}
|
||||
|
||||
attrs = slices.Grow(attrs, n)
|
||||
|
||||
attrs = append(attrs, c.method(req.Method))
|
||||
attrs = append(attrs, c.scheme(req.TLS != nil))
|
||||
attrs = append(attrs, c.NetConv.HostName(host))
|
||||
|
||||
if hostPort > 0 {
|
||||
attrs = append(attrs, c.NetConv.HostPort(hostPort))
|
||||
}
|
||||
|
||||
if peer != "" {
|
||||
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
|
||||
// file-path that would be interpreted with a sock family.
|
||||
attrs = append(attrs, c.NetConv.SockPeerAddr(peer))
|
||||
if peerPort > 0 {
|
||||
attrs = append(attrs, c.NetConv.SockPeerPort(peerPort))
|
||||
}
|
||||
}
|
||||
|
||||
if useragent != "" {
|
||||
attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
|
||||
}
|
||||
|
||||
if clientIP != "" {
|
||||
attrs = append(attrs, c.HTTPClientIPKey.String(clientIP))
|
||||
}
|
||||
|
||||
if target != "" {
|
||||
attrs = append(attrs, c.HTTPTargetKey.String(target))
|
||||
}
|
||||
|
||||
if protoName != "" && protoName != "http" {
|
||||
attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ServerRequestMetrics returns metric attributes for an HTTP request received
|
||||
// by a server.
|
||||
//
|
||||
// The server must be the primary server name if it is known. For example this
|
||||
// would be the ServerName directive
|
||||
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
|
||||
// server, and the server_name directive
|
||||
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
|
||||
// nginx server. More generically, the primary server name would be the host
|
||||
// header value that matches the default virtual host of an HTTP server. It
|
||||
// should include the host identifier and if a port is used to route to the
|
||||
// server that port identifier should be included as an appropriate port
|
||||
// suffix.
|
||||
//
|
||||
// If the primary server name is not known, server should be an empty string.
|
||||
// The req Host will be used to determine the server instead.
|
||||
//
|
||||
// The following attributes are always returned: "http.method", "http.scheme",
|
||||
// "net.host.name". The following attributes are returned if they related
|
||||
// values are defined in req: "net.host.port".
|
||||
func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue {
|
||||
/* The following semantic conventions are returned if present:
|
||||
http.scheme string
|
||||
http.route string
|
||||
http.method string
|
||||
http.status_code int
|
||||
net.host.name string
|
||||
net.host.port int
|
||||
net.protocol.name string Note: not set if the value is "http".
|
||||
net.protocol.version string
|
||||
*/
|
||||
|
||||
n := 3 // Method, scheme, and host name.
|
||||
var host string
|
||||
var p int
|
||||
if server == "" {
|
||||
host, p = splitHostPort(req.Host)
|
||||
} else {
|
||||
// Prioritize the primary server name.
|
||||
host, p = splitHostPort(server)
|
||||
if p < 0 {
|
||||
_, p = splitHostPort(req.Host)
|
||||
}
|
||||
}
|
||||
hostPort := requiredHTTPPort(req.TLS != nil, p)
|
||||
if hostPort > 0 {
|
||||
n++
|
||||
}
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
n++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
n++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, n)
|
||||
|
||||
attrs = append(attrs, c.methodMetric(req.Method))
|
||||
attrs = append(attrs, c.scheme(req.TLS != nil))
|
||||
attrs = append(attrs, c.NetConv.HostName(host))
|
||||
|
||||
if hostPort > 0 {
|
||||
attrs = append(attrs, c.NetConv.HostPort(hostPort))
|
||||
}
|
||||
if protoName != "" {
|
||||
attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (c *httpConv) method(method string) attribute.KeyValue {
|
||||
if method == "" {
|
||||
return c.HTTPMethodKey.String(http.MethodGet)
|
||||
}
|
||||
return c.HTTPMethodKey.String(method)
|
||||
}
|
||||
|
||||
func (c *httpConv) methodMetric(method string) attribute.KeyValue {
|
||||
method = strings.ToUpper(method)
|
||||
switch method {
|
||||
case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
|
||||
default:
|
||||
method = "_OTHER"
|
||||
}
|
||||
return c.HTTPMethodKey.String(method)
|
||||
}
|
||||
|
||||
func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive
|
||||
if https {
|
||||
return c.HTTPSchemeHTTPS
|
||||
}
|
||||
return c.HTTPSchemeHTTP
|
||||
}
|
||||
|
||||
func serverClientIP(xForwardedFor string) string {
|
||||
if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
|
||||
xForwardedFor = xForwardedFor[:idx]
|
||||
}
|
||||
return xForwardedFor
|
||||
}
|
||||
|
||||
func requiredHTTPPort(https bool, port int) int { // nolint:revive
|
||||
if https {
|
||||
if port > 0 && port != 443 {
|
||||
return port
|
||||
}
|
||||
} else {
|
||||
if port > 0 && port != 80 {
|
||||
return port
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Return the request host and port from the first non-empty source.
|
||||
func firstHostPort(source ...string) (host string, port int) {
|
||||
for _, hostport := range source {
|
||||
host, port = splitHostPort(hostport)
|
||||
if host != "" || port > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClientStatus returns a span status code and message for an HTTP status code
|
||||
// value received by a client.
|
||||
func (c *httpConv) ClientStatus(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 400 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
// ServerStatus returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func (c *httpConv) ServerStatus(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 500 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go
Generated
Vendored
-214
@@ -1,214 +0,0 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconvutil/netconv.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
|
||||
)
|
||||
|
||||
// NetTransport returns a trace attribute describing the transport protocol of the
|
||||
// passed network. See the net.Dial for information about acceptable network
|
||||
// values.
|
||||
func NetTransport(network string) attribute.KeyValue {
|
||||
return nc.Transport(network)
|
||||
}
|
||||
|
||||
// netConv are the network semantic convention attributes defined for a version
|
||||
// of the OpenTelemetry specification.
|
||||
type netConv struct {
|
||||
NetHostNameKey attribute.Key
|
||||
NetHostPortKey attribute.Key
|
||||
NetPeerNameKey attribute.Key
|
||||
NetPeerPortKey attribute.Key
|
||||
NetProtocolName attribute.Key
|
||||
NetProtocolVersion attribute.Key
|
||||
NetSockFamilyKey attribute.Key
|
||||
NetSockPeerAddrKey attribute.Key
|
||||
NetSockPeerPortKey attribute.Key
|
||||
NetSockHostAddrKey attribute.Key
|
||||
NetSockHostPortKey attribute.Key
|
||||
NetTransportOther attribute.KeyValue
|
||||
NetTransportTCP attribute.KeyValue
|
||||
NetTransportUDP attribute.KeyValue
|
||||
NetTransportInProc attribute.KeyValue
|
||||
}
|
||||
|
||||
var nc = &netConv{
|
||||
NetHostNameKey: semconv.NetHostNameKey,
|
||||
NetHostPortKey: semconv.NetHostPortKey,
|
||||
NetPeerNameKey: semconv.NetPeerNameKey,
|
||||
NetPeerPortKey: semconv.NetPeerPortKey,
|
||||
NetProtocolName: semconv.NetProtocolNameKey,
|
||||
NetProtocolVersion: semconv.NetProtocolVersionKey,
|
||||
NetSockFamilyKey: semconv.NetSockFamilyKey,
|
||||
NetSockPeerAddrKey: semconv.NetSockPeerAddrKey,
|
||||
NetSockPeerPortKey: semconv.NetSockPeerPortKey,
|
||||
NetSockHostAddrKey: semconv.NetSockHostAddrKey,
|
||||
NetSockHostPortKey: semconv.NetSockHostPortKey,
|
||||
NetTransportOther: semconv.NetTransportOther,
|
||||
NetTransportTCP: semconv.NetTransportTCP,
|
||||
NetTransportUDP: semconv.NetTransportUDP,
|
||||
NetTransportInProc: semconv.NetTransportInProc,
|
||||
}
|
||||
|
||||
func (c *netConv) Transport(network string) attribute.KeyValue {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
return c.NetTransportTCP
|
||||
case "udp", "udp4", "udp6":
|
||||
return c.NetTransportUDP
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
return c.NetTransportInProc
|
||||
default:
|
||||
// "ip:*", "ip4:*", and "ip6:*" all are considered other.
|
||||
return c.NetTransportOther
|
||||
}
|
||||
}
|
||||
|
||||
// Host returns attributes for a network host address.
|
||||
func (c *netConv) Host(address string) []attribute.KeyValue {
|
||||
h, p := splitHostPort(address)
|
||||
var n int
|
||||
if h != "" {
|
||||
n++
|
||||
if p > 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, n)
|
||||
attrs = append(attrs, c.HostName(h))
|
||||
if p > 0 {
|
||||
attrs = append(attrs, c.HostPort(p))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (c *netConv) HostName(name string) attribute.KeyValue {
|
||||
return c.NetHostNameKey.String(name)
|
||||
}
|
||||
|
||||
func (c *netConv) HostPort(port int) attribute.KeyValue {
|
||||
return c.NetHostPortKey.Int(port)
|
||||
}
|
||||
|
||||
func family(network, address string) string {
|
||||
switch network {
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
return "unix"
|
||||
default:
|
||||
if ip := net.ParseIP(address); ip != nil {
|
||||
if ip.To4() == nil {
|
||||
return "inet6"
|
||||
}
|
||||
return "inet"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Peer returns attributes for a network peer address.
|
||||
func (c *netConv) Peer(address string) []attribute.KeyValue {
|
||||
h, p := splitHostPort(address)
|
||||
var n int
|
||||
if h != "" {
|
||||
n++
|
||||
if p > 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, n)
|
||||
attrs = append(attrs, c.PeerName(h))
|
||||
if p > 0 {
|
||||
attrs = append(attrs, c.PeerPort(p))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (c *netConv) PeerName(name string) attribute.KeyValue {
|
||||
return c.NetPeerNameKey.String(name)
|
||||
}
|
||||
|
||||
func (c *netConv) PeerPort(port int) attribute.KeyValue {
|
||||
return c.NetPeerPortKey.Int(port)
|
||||
}
|
||||
|
||||
func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue {
|
||||
return c.NetSockPeerAddrKey.String(addr)
|
||||
}
|
||||
|
||||
func (c *netConv) SockPeerPort(port int) attribute.KeyValue {
|
||||
return c.NetSockPeerPortKey.Int(port)
|
||||
}
|
||||
|
||||
// splitHostPort splits a network address hostport of the form "host",
|
||||
// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port",
|
||||
// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and
|
||||
// port.
|
||||
//
|
||||
// An empty host is returned if it is not provided or unparsable. A negative
|
||||
// port is returned if it is not provided or unparsable.
|
||||
func splitHostPort(hostport string) (host string, port int) {
|
||||
port = -1
|
||||
|
||||
if strings.HasPrefix(hostport, "[") {
|
||||
addrEnd := strings.LastIndex(hostport, "]")
|
||||
if addrEnd < 0 {
|
||||
// Invalid hostport.
|
||||
return
|
||||
}
|
||||
if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 {
|
||||
host = hostport[1:addrEnd]
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if i := strings.LastIndex(hostport, ":"); i < 0 {
|
||||
host = hostport
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
host, pStr, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p, err := strconv.ParseUint(pStr, 10, 16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return host, int(p) // nolint: gosec // Bitsize checked to be 16 above.
|
||||
}
|
||||
|
||||
func netProtocol(proto string) (name string, version string) {
|
||||
name, version, _ = strings.Cut(proto, "/")
|
||||
switch name {
|
||||
case "HTTP":
|
||||
name = "http"
|
||||
case "QUIC":
|
||||
name = "quic"
|
||||
case "SPDY":
|
||||
name = "spdy"
|
||||
default:
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
return name, version
|
||||
}
|
||||
Generated
Vendored
+47
-42
@@ -5,20 +5,22 @@ package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
|
||||
otelsemconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
)
|
||||
|
||||
// Transport implements the http.RoundTripper interface and wraps
|
||||
@@ -83,6 +85,8 @@ func defaultTransportFormatter(_ string, r *http.Request) string {
|
||||
// RoundTrip creates a Span and propagates its context via the provided request's headers
|
||||
// before handing the request to the configured base RoundTripper. The created span will
|
||||
// end when the response body is closed or when a read from the body returns io.EOF.
|
||||
// If GetBody returns an error, the error is reported via otel.Handle and the request
|
||||
// continues with the original Body.
|
||||
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
requestStartTime := time.Now()
|
||||
for _, f := range t.filters {
|
||||
@@ -102,9 +106,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
}
|
||||
}
|
||||
|
||||
opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options
|
||||
|
||||
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
||||
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), t.spanStartOptions...)
|
||||
|
||||
if t.clientTrace != nil {
|
||||
ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx))
|
||||
@@ -117,11 +119,22 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
|
||||
r = r.Clone(ctx) // According to RoundTripper spec, we shouldn't modify the origin request.
|
||||
|
||||
// if request body is nil or NoBody, we don't want to mutate the body as it
|
||||
// GetBody is preferred over direct access to Body if the function is set.
|
||||
// If the resulting body is nil or is NoBody, we don't want to mutate the body as it
|
||||
// will affect the identity of it in an unforeseeable way because we assert
|
||||
// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
|
||||
bw := request.NewBodyWrapper(r.Body, func(int64) {})
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
body := r.Body
|
||||
if r.GetBody != nil {
|
||||
b, err := r.GetBody()
|
||||
if err != nil {
|
||||
otel.Handle(fmt.Errorf("http.Request GetBody returned an error: %w", err))
|
||||
} else {
|
||||
body = b
|
||||
}
|
||||
}
|
||||
|
||||
bw := request.NewBodyWrapper(body, func(int64) {})
|
||||
if body != nil && body != http.NoBody {
|
||||
r.Body = bw
|
||||
}
|
||||
|
||||
@@ -129,47 +142,39 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
|
||||
|
||||
res, err := t.rt.RoundTrip(r)
|
||||
if err != nil {
|
||||
// set error type attribute if the error is part of the predefined
|
||||
// error types.
|
||||
// otherwise, record it as an exception
|
||||
if errType := t.semconv.ErrorType(err); errType.Valid() {
|
||||
span.SetAttributes(errType)
|
||||
} else {
|
||||
span.RecordError(err)
|
||||
}
|
||||
|
||||
// Record the metrics on error or no error.
|
||||
statusCode := 0
|
||||
if err == nil {
|
||||
statusCode = res.StatusCode
|
||||
}
|
||||
t.semconv.RecordMetrics(
|
||||
ctx,
|
||||
semconv.MetricData{
|
||||
RequestSize: bw.BytesRead(),
|
||||
RequestDuration: time.Since(requestStartTime),
|
||||
},
|
||||
t.semconv.MetricOptions(semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: statusCode,
|
||||
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
|
||||
}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
span.SetAttributes(otelsemconv.ErrorType(err))
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
span.End()
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// metrics
|
||||
metricOpts := t.semconv.MetricOptions(semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: res.StatusCode,
|
||||
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
|
||||
})
|
||||
|
||||
// For handling response bytes we leverage a callback when the client reads the http response
|
||||
readRecordFunc := func(n int64) {
|
||||
t.semconv.RecordResponseSize(ctx, n, metricOpts)
|
||||
}
|
||||
|
||||
readRecordFunc := func(int64) {}
|
||||
res.Body = newWrappedBody(span, readRecordFunc, res.Body)
|
||||
// traces
|
||||
span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...)
|
||||
span.SetStatus(t.semconv.Status(res.StatusCode))
|
||||
|
||||
res.Body = newWrappedBody(span, readRecordFunc, res.Body)
|
||||
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
|
||||
|
||||
t.semconv.RecordMetrics(ctx, semconv.MetricData{
|
||||
RequestSize: bw.BytesRead(),
|
||||
ElapsedTime: elapsedTime,
|
||||
}, metricOpts)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -219,7 +224,7 @@ func (wb *wrappedBody) Write(p []byte) (int, error) {
|
||||
// This will not panic given the guard in newWrappedBody.
|
||||
n, err := wb.body.(io.Writer).Write(p)
|
||||
if err != nil {
|
||||
wb.span.RecordError(err)
|
||||
wb.span.SetAttributes(otelsemconv.ErrorType(err))
|
||||
wb.span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
return n, err
|
||||
@@ -237,7 +242,7 @@ func (wb *wrappedBody) Read(b []byte) (int, error) {
|
||||
wb.recordBytesRead()
|
||||
wb.span.End()
|
||||
default:
|
||||
wb.span.RecordError(err)
|
||||
wb.span.SetAttributes(otelsemconv.ErrorType(err))
|
||||
wb.span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
return n, err
|
||||
|
||||
+1
-4
@@ -4,7 +4,4 @@
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
// Version is the current release version of the otelhttp instrumentation.
|
||||
func Version() string {
|
||||
return "0.61.0"
|
||||
// This string is updated by the pre_release.sh script during release
|
||||
}
|
||||
const Version = "0.66.0"
|
||||
|
||||
+1
@@ -194,6 +194,7 @@ linters:
|
||||
arguments:
|
||||
- ["ID"] # AllowList
|
||||
- ["Otel", "Aws", "Gcp"] # DenyList
|
||||
- - skip-package-name-collision-with-go-std: true
|
||||
- name: waitgroup-by-value
|
||||
testifylint:
|
||||
enable-all: true
|
||||
|
||||
+21
-1
@@ -8,6 +8,24 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.41.0/0.63.0/0.17.0/0.0.15] 2026-03-02
|
||||
|
||||
This release is the last to support [Go 1.24].
|
||||
The next release will require at least [Go 1.25].
|
||||
|
||||
### Added
|
||||
|
||||
- Support testing of [Go 1.26]. (#7902)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Update `Baggage` in `go.opentelemetry.io/otel/propagation` and `Parse` and `New` in `go.opentelemetry.io/otel/baggage` to comply with W3C Baggage specification limits.
|
||||
`New` and `Parse` now return partial baggage along with an error when limits are exceeded.
|
||||
Errors from baggage extraction are reported to the global error handler. (#7880)
|
||||
- Return an error when the endpoint is configured as insecure and with TLS configuration in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#7914)
|
||||
- Return an error when the endpoint is configured as insecure and with TLS configuration in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#7914)
|
||||
- Return an error when the endpoint is configured as insecure and with TLS configuration in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#7914)
|
||||
|
||||
<!-- Released section -->
|
||||
<!-- Don't change this section unless doing release -->
|
||||
|
||||
@@ -3535,7 +3553,8 @@ It contains api and sdk for trace and meter.
|
||||
- CircleCI build CI manifest files.
|
||||
- CODEOWNERS file to track owners of this project.
|
||||
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.40.0...HEAD
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.41.0...HEAD
|
||||
[1.41.0/0.63.0/0.17.0/0.0.15]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.41.0
|
||||
[1.40.0/0.62.0/0.16.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.40.0
|
||||
[1.39.0/0.61.0/0.15.0/0.0.14]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.39.0
|
||||
[1.38.0/0.60.0/0.14.0/0.0.13]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.38.0
|
||||
@@ -3635,6 +3654,7 @@ It contains api and sdk for trace and meter.
|
||||
|
||||
<!-- Released section ended -->
|
||||
|
||||
[Go 1.26]: https://go.dev/doc/go1.26
|
||||
[Go 1.25]: https://go.dev/doc/go1.25
|
||||
[Go 1.24]: https://go.dev/doc/go1.24
|
||||
[Go 1.23]: https://go.dev/doc/go1.23
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ go-mod-tidy/%: crosslink
|
||||
&& $(GO) mod tidy -compat=1.21
|
||||
|
||||
.PHONY: lint
|
||||
lint: misspell go-mod-tidy golangci-lint govulncheck
|
||||
lint: misspell go-mod-tidy golangci-lint
|
||||
|
||||
.PHONY: vanity-import-check
|
||||
vanity-import-check: $(PORTO)
|
||||
|
||||
+7
@@ -53,18 +53,25 @@ Currently, this project supports the following environments.
|
||||
|
||||
| OS | Go Version | Architecture |
|
||||
|----------|------------|--------------|
|
||||
| Ubuntu | 1.26 | amd64 |
|
||||
| Ubuntu | 1.25 | amd64 |
|
||||
| Ubuntu | 1.24 | amd64 |
|
||||
| Ubuntu | 1.26 | 386 |
|
||||
| Ubuntu | 1.25 | 386 |
|
||||
| Ubuntu | 1.24 | 386 |
|
||||
| Ubuntu | 1.26 | arm64 |
|
||||
| Ubuntu | 1.25 | arm64 |
|
||||
| Ubuntu | 1.24 | arm64 |
|
||||
| macOS | 1.26 | amd64 |
|
||||
| macOS | 1.25 | amd64 |
|
||||
| macOS | 1.24 | amd64 |
|
||||
| macOS | 1.26 | arm64 |
|
||||
| macOS | 1.25 | arm64 |
|
||||
| macOS | 1.24 | arm64 |
|
||||
| Windows | 1.26 | amd64 |
|
||||
| Windows | 1.25 | amd64 |
|
||||
| Windows | 1.24 | amd64 |
|
||||
| Windows | 1.26 | 386 |
|
||||
| Windows | 1.25 | 386 |
|
||||
| Windows | 1.24 | 386 |
|
||||
|
||||
|
||||
+83
-26
@@ -14,8 +14,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxMembers = 180
|
||||
maxBytesPerMembers = 4096
|
||||
maxMembers = 64
|
||||
maxBytesPerBaggageString = 8192
|
||||
|
||||
listDelimiter = ","
|
||||
@@ -29,7 +28,6 @@ var (
|
||||
errInvalidProperty = errors.New("invalid baggage list-member property")
|
||||
errInvalidMember = errors.New("invalid baggage list-member")
|
||||
errMemberNumber = errors.New("too many list-members in baggage-string")
|
||||
errMemberBytes = errors.New("list-member too large")
|
||||
errBaggageBytes = errors.New("baggage-string too large")
|
||||
)
|
||||
|
||||
@@ -309,10 +307,6 @@ func newInvalidMember() Member {
|
||||
// an error if the input is invalid according to the W3C Baggage
|
||||
// specification.
|
||||
func parseMember(member string) (Member, error) {
|
||||
if n := len(member); n > maxBytesPerMembers {
|
||||
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
|
||||
}
|
||||
|
||||
var props properties
|
||||
keyValue, properties, found := strings.Cut(member, propertyDelimiter)
|
||||
if found {
|
||||
@@ -430,6 +424,10 @@ type Baggage struct { //nolint:golint
|
||||
// New returns a new valid Baggage. It returns an error if it results in a
|
||||
// Baggage exceeding limits set in that specification.
|
||||
//
|
||||
// If the resulting Baggage exceeds the maximum allowed members or bytes,
|
||||
// members are dropped until the limits are satisfied and an error is returned
|
||||
// along with the partial result.
|
||||
//
|
||||
// It expects all the provided members to have already been validated.
|
||||
func New(members ...Member) (Baggage, error) {
|
||||
if len(members) == 0 {
|
||||
@@ -441,7 +439,6 @@ func New(members ...Member) (Baggage, error) {
|
||||
if !m.hasData {
|
||||
return Baggage{}, errInvalidMember
|
||||
}
|
||||
|
||||
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||
b[m.key] = baggage.Item{
|
||||
Value: m.value,
|
||||
@@ -449,17 +446,42 @@ func New(members ...Member) (Baggage, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check member numbers after deduplication.
|
||||
var truncateErr error
|
||||
|
||||
// Check member count after deduplication.
|
||||
if len(b) > maxMembers {
|
||||
return Baggage{}, errMemberNumber
|
||||
truncateErr = errors.Join(truncateErr, errMemberNumber)
|
||||
for k := range b {
|
||||
if len(b) <= maxMembers {
|
||||
break
|
||||
}
|
||||
delete(b, k)
|
||||
}
|
||||
}
|
||||
|
||||
bag := Baggage{b}
|
||||
if n := len(bag.String()); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
// Check byte size and drop members if necessary.
|
||||
totalBytes := 0
|
||||
first := true
|
||||
for k := range b {
|
||||
m := Member{
|
||||
key: k,
|
||||
value: b[k].Value,
|
||||
properties: fromInternalProperties(b[k].Properties),
|
||||
}
|
||||
memberSize := len(m.String())
|
||||
if !first {
|
||||
memberSize++ // comma separator
|
||||
}
|
||||
if totalBytes+memberSize > maxBytesPerBaggageString {
|
||||
truncateErr = errors.Join(truncateErr, fmt.Errorf("%w: %d", errBaggageBytes, totalBytes+memberSize))
|
||||
delete(b, k)
|
||||
continue
|
||||
}
|
||||
totalBytes += memberSize
|
||||
first = false
|
||||
}
|
||||
|
||||
return bag, nil
|
||||
return Baggage{b}, truncateErr
|
||||
}
|
||||
|
||||
// Parse attempts to decode a baggage-string from the passed string. It
|
||||
@@ -470,36 +492,71 @@ func New(members ...Member) (Baggage, error) {
|
||||
// defined (reading left-to-right) will be the only one kept. This diverges
|
||||
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||
// conforms to the OpenTelemetry Baggage specification.
|
||||
//
|
||||
// If the baggage-string exceeds the maximum allowed members (64) or bytes
|
||||
// (8192), members are dropped until the limits are satisfied and an error is
|
||||
// returned along with the partial result.
|
||||
//
|
||||
// Invalid members are skipped and the error is returned along with the
|
||||
// partial result containing the valid members.
|
||||
func Parse(bStr string) (Baggage, error) {
|
||||
if bStr == "" {
|
||||
return Baggage{}, nil
|
||||
}
|
||||
|
||||
if n := len(bStr); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
}
|
||||
|
||||
b := make(baggage.List)
|
||||
sizes := make(map[string]int) // Track per-key byte sizes
|
||||
var totalBytes int
|
||||
var truncateErr error
|
||||
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
|
||||
// Check member count limit.
|
||||
if len(b) >= maxMembers {
|
||||
truncateErr = errors.Join(truncateErr, errMemberNumber)
|
||||
break
|
||||
}
|
||||
|
||||
m, err := parseMember(memberStr)
|
||||
if err != nil {
|
||||
return Baggage{}, err
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
continue // skip invalid member, keep processing
|
||||
}
|
||||
|
||||
// Check byte size limit.
|
||||
// Account for comma separator between members.
|
||||
memberBytes := len(m.String())
|
||||
_, existingKey := b[m.key]
|
||||
if !existingKey && len(b) > 0 {
|
||||
memberBytes++ // comma separator only for new keys
|
||||
}
|
||||
|
||||
// Calculate new totalBytes if we add/overwrite this key
|
||||
var newTotalBytes int
|
||||
if oldSize, exists := sizes[m.key]; exists {
|
||||
// Overwriting existing key: subtract old size, add new size
|
||||
newTotalBytes = totalBytes - oldSize + memberBytes
|
||||
} else {
|
||||
// New key
|
||||
newTotalBytes = totalBytes + memberBytes
|
||||
}
|
||||
|
||||
if newTotalBytes > maxBytesPerBaggageString {
|
||||
truncateErr = errors.Join(truncateErr, errBaggageBytes)
|
||||
break
|
||||
}
|
||||
|
||||
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||
b[m.key] = baggage.Item{
|
||||
Value: m.value,
|
||||
Properties: m.properties.asInternal(),
|
||||
}
|
||||
sizes[m.key] = memberBytes
|
||||
totalBytes = newTotalBytes
|
||||
}
|
||||
|
||||
// OpenTelemetry does not allow for duplicate list-members, but the W3C
|
||||
// specification does. Now that we have deduplicated, ensure the baggage
|
||||
// does not exceed list-member limits.
|
||||
if len(b) > maxMembers {
|
||||
return Baggage{}, errMemberNumber
|
||||
if len(b) == 0 {
|
||||
return Baggage{}, truncateErr
|
||||
}
|
||||
|
||||
return Baggage{b}, nil
|
||||
return Baggage{b}, truncateErr
|
||||
}
|
||||
|
||||
// Member returns the baggage list-member identified by key.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# This is a renovate-friendly source of Docker images.
|
||||
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
|
||||
FROM otel/weaver:v0.20.0@sha256:fa4f1c6954ecea78ab1a4e865bd6f5b4aaba80c1896f9f4a11e2c361d04e197e AS weaver
|
||||
FROM otel/weaver:v0.21.2@sha256:2401de985c38bdb98b43918e2f43aa36b2afed4aa5669ac1c1de0a17301cd36d AS weaver
|
||||
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
|
||||
|
||||
+30
@@ -199,3 +199,33 @@
|
||||
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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+1
-1
@@ -94,7 +94,7 @@ func NewUnstarted(client Client) *Exporter {
|
||||
}
|
||||
|
||||
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
|
||||
func (e *Exporter) MarshalLog() interface{} {
|
||||
func (e *Exporter) MarshalLog() any {
|
||||
return struct {
|
||||
Type string
|
||||
Client Client
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -6,9 +6,10 @@
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
)
|
||||
|
||||
// KeyValues transforms a slice of attribute KeyValues into OTLP key-values.
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -4,8 +4,9 @@
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
)
|
||||
|
||||
func InstrumentationScope(il instrumentation.Scope) *commonpb.InstrumentationScope {
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -4,8 +4,9 @@
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Resource transforms a Resource into an OTLP Resource.
|
||||
|
||||
Generated
Vendored
+12
-10
@@ -6,12 +6,13 @@ package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptr
|
||||
import (
|
||||
"math"
|
||||
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
// Spans transforms a slice of OpenTelemetry spans into a slice of OTLP
|
||||
@@ -112,7 +113,7 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
|
||||
if psid := sd.Parent().SpanID(); psid.IsValid() {
|
||||
s.ParentSpanId = psid[:]
|
||||
}
|
||||
s.Flags = buildSpanFlags(sd.Parent())
|
||||
s.Flags = buildSpanFlagsWith(sd.SpanContext().TraceFlags(), sd.Parent())
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -154,12 +155,11 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
|
||||
for _, otLink := range links {
|
||||
// This redefinition is necessary to prevent otLink.*ID[:] copies
|
||||
// being reused -- in short we need a new otLink per iteration.
|
||||
otLink := otLink
|
||||
|
||||
tid := otLink.SpanContext.TraceID()
|
||||
sid := otLink.SpanContext.SpanID()
|
||||
|
||||
flags := buildSpanFlags(otLink.SpanContext)
|
||||
flags := buildSpanFlagsWith(otLink.SpanContext.TraceFlags(), otLink.SpanContext)
|
||||
|
||||
sl = append(sl, &tracepb.Span_Link{
|
||||
TraceId: tid[:],
|
||||
@@ -172,13 +172,15 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
|
||||
return sl
|
||||
}
|
||||
|
||||
func buildSpanFlags(sc trace.SpanContext) uint32 {
|
||||
flags := tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
|
||||
if sc.IsRemote() {
|
||||
flags |= tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
|
||||
func buildSpanFlagsWith(tf trace.TraceFlags, parent trace.SpanContext) uint32 {
|
||||
// Lower 8 bits are the W3C TraceFlags; always indicate that we know whether the parent is remote
|
||||
flags := uint32(tf) | uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK)
|
||||
// Set the parent-is-remote bit when applicable
|
||||
if parent.IsRemote() {
|
||||
flags |= uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK)
|
||||
}
|
||||
|
||||
return uint32(flags) // nolint:gosec // Flags is a bitmask and can't be negative
|
||||
return flags // nolint:gosec // Flags is a bitmask and can't be negative
|
||||
}
|
||||
|
||||
// spanEvents transforms span Events to an OTLP span events.
|
||||
@@ -189,7 +191,7 @@ func spanEvents(es []tracesdk.Event) []*tracepb.Span_Event {
|
||||
|
||||
events := make([]*tracepb.Span_Event, len(es))
|
||||
// Transform message events
|
||||
for i := 0; i < len(es); i++ {
|
||||
for i := range es {
|
||||
events[i] = &tracepb.Span_Event{
|
||||
Name: es[i].Name,
|
||||
TimeUnixNano: uint64(max(0, es[i].Time.UnixNano())), // nolint:gosec // Overflow checked.
|
||||
|
||||
+30
@@ -199,3 +199,33 @@
|
||||
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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+35
-12
@@ -9,19 +9,20 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
@@ -45,6 +46,9 @@ type client struct {
|
||||
conn *grpc.ClientConn
|
||||
tscMu sync.RWMutex
|
||||
tsc coltracepb.TraceServiceClient
|
||||
|
||||
instID int64
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
// Compile time check *client implements otlptrace.Client.
|
||||
@@ -68,6 +72,7 @@ func newClient(opts ...Option) *client {
|
||||
stopCtx: ctx,
|
||||
stopFunc: cancel,
|
||||
conn: cfg.GRPCConn,
|
||||
instID: counter.NextExporterID(),
|
||||
}
|
||||
|
||||
if len(cfg.Traces.Headers) > 0 {
|
||||
@@ -92,13 +97,24 @@ func (c *client) Start(context.Context) error {
|
||||
c.conn = conn
|
||||
}
|
||||
|
||||
// Initialize the instrumentation if not already done.
|
||||
//
|
||||
// Initialize here instead of NewClient to allow any errors to be passed
|
||||
// back to the caller and so that any setup of the environment variables to
|
||||
// enable instrumentation can be set via code.
|
||||
var err error
|
||||
if c.inst == nil {
|
||||
target := c.conn.CanonicalTarget()
|
||||
c.inst, err = observ.NewInstrumentation(c.instID, target)
|
||||
}
|
||||
|
||||
// The otlptrace.Client interface states this method is called just once,
|
||||
// so no need to check if already started.
|
||||
c.tscMu.Lock()
|
||||
c.tsc = coltracepb.NewTraceServiceClient(c.conn)
|
||||
c.tscMu.Unlock()
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
var errAlreadyStopped = errors.New("the client is already stopped")
|
||||
@@ -174,7 +190,7 @@ var errShutdown = errors.New("the client is shutdown")
|
||||
//
|
||||
// Retryable errors from the server will be handled according to any
|
||||
// RetryConfig the client was created with.
|
||||
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) error {
|
||||
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) (uploadErr error) {
|
||||
// Hold a read lock to ensure a shut down initiated after this starts does
|
||||
// not abandon the export. This read lock acquire has less priority than a
|
||||
// write lock acquire (i.e. Stop), meaning if the client is shutting down
|
||||
@@ -189,6 +205,12 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
var code codes.Code
|
||||
if c.inst != nil {
|
||||
op := c.inst.ExportSpans(ctx, len(protoSpans))
|
||||
defer func() { op.End(uploadErr, code) }()
|
||||
}
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.tsc.Export(iCtx, &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
@@ -197,16 +219,17 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedSpans()
|
||||
if n != 0 || msg != "" {
|
||||
err := internal.TracePartialSuccessError(n, msg)
|
||||
otel.Handle(err)
|
||||
e := internal.TracePartialSuccessError(n, msg)
|
||||
uploadErr = errors.Join(uploadErr, e)
|
||||
}
|
||||
}
|
||||
// nil is converted to OK.
|
||||
if status.Code(err) == codes.OK {
|
||||
code = status.Code(err)
|
||||
if code == codes.OK {
|
||||
// Success.
|
||||
return nil
|
||||
return uploadErr
|
||||
}
|
||||
return err
|
||||
return errors.Join(uploadErr, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -223,7 +246,7 @@ func (c *client) exportContext(parent context.Context) (context.Context, context
|
||||
)
|
||||
|
||||
if c.exportTimeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(parent, c.exportTimeout)
|
||||
ctx, cancel = context.WithTimeoutCause(parent, c.exportTimeout, errors.New("exporter export timeout"))
|
||||
} else {
|
||||
ctx, cancel = context.WithCancel(parent)
|
||||
}
|
||||
@@ -289,7 +312,7 @@ func throttleDelay(s *status.Status) (bool, time.Duration) {
|
||||
}
|
||||
|
||||
// MarshalLog is the marshaling function used by the logging system to represent this Client.
|
||||
func (c *client) MarshalLog() interface{} {
|
||||
func (c *client) MarshalLog() any {
|
||||
return struct {
|
||||
Type string
|
||||
Endpoint string
|
||||
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/counter/counter.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package counter provides a simple counter for generating unique IDs.
|
||||
//
|
||||
// This package is used to generate unique IDs while allowing testing packages
|
||||
// to reset the counter.
|
||||
package counter // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter"
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// exporterN is a global 0-based count of the number of exporters created.
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// NextExporterID returns the next unique ID for an exporter.
|
||||
func NextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetExporterID sets the exporter ID counter to v and returns the previous
|
||||
// value.
|
||||
//
|
||||
// This function is useful for testing purposes, allowing you to reset the
|
||||
// counter. It should not be used in production code.
|
||||
func SetExporterID(v int64) int64 {
|
||||
return exporterN.Swap(v)
|
||||
}
|
||||
Generated
Vendored
+9
@@ -23,3 +23,12 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/collector.go.tmpl "--data={}" --out=otlptracetest/collector.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/data.go.tmpl "--data={}" --out=otlptracetest/data.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/otlptest.go.tmpl "--data={}" --out=otlptracetest/otlptest.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ\" }" --out=observ/target.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target_test.go.tmpl "--data={ \"pkg\": \"observ\" }" --out=observ/target_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides experimental observability instrumentation for the
|
||||
// otlptracegrpc exporter.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
Generated
Vendored
+350
@@ -0,0 +1,350 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
|
||||
// SchemaURL is the schema URL of the metrics produced by this
|
||||
// instrumentation.
|
||||
SchemaURL = semconv.SchemaURL
|
||||
|
||||
// Version is the current version of this instrumentation.
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
var (
|
||||
measureAttrsPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 + // component.name
|
||||
1 + // component.type
|
||||
1 + // server.addr
|
||||
1 + // server.port
|
||||
1 + // error.type
|
||||
1 // rpc.grpc.status_code
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
// Return a pointer to a slice instead of a slice itself
|
||||
// to avoid allocations on every call.
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
addOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.AddOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
|
||||
recordOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.RecordOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
// ComponentName returns the component name for the exporter with the
|
||||
// provided ID.
|
||||
func ComponentName(id int64) string {
|
||||
t := semconv.OTelComponentTypeOtlpGRPCSpanExporter.Value.AsString()
|
||||
return fmt.Sprintf("%s/%d", t, id)
|
||||
}
|
||||
|
||||
// Instrumentation is experimental instrumentation for the exporter.
|
||||
type Instrumentation struct {
|
||||
inflightSpans metric.Int64UpDownCounter
|
||||
exportedSpans metric.Int64Counter
|
||||
opDuration metric.Float64Histogram
|
||||
|
||||
attrs []attribute.KeyValue
|
||||
addOpt metric.AddOption
|
||||
recOpt metric.RecordOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for an OTLP over gPRC trace
|
||||
// exporter with the provided ID using the global MeterProvider.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the endpoint the exporter is exporting to.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attrs := BaseAttrs(id, target)
|
||||
i := &Instrumentation{
|
||||
attrs: attrs,
|
||||
addOpt: metric.WithAttributeSet(attribute.NewSet(attrs...)),
|
||||
|
||||
// Do not modify attrs (NewSet sorts in-place), make a new slice.
|
||||
recOpt: metric.WithAttributeSet(attribute.NewSet(append(
|
||||
// Default to OK status code.
|
||||
[]attribute.KeyValue{
|
||||
semconv.RPCResponseStatusCode(codes.OK.String()),
|
||||
},
|
||||
attrs...,
|
||||
)...)),
|
||||
}
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.inflightSpans = inflightSpans.Inst()
|
||||
|
||||
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.exportedSpans = exportedSpans.Inst()
|
||||
|
||||
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.opDuration = opDuration.Inst()
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
// BaseAttrs returns the base attributes for the exporter with the provided ID
|
||||
// and target.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the gRPC target the exporter is exporting to. It is expected
|
||||
// to be the output of the Client's CanonicalTarget method.
|
||||
func BaseAttrs(id int64, target string) []attribute.KeyValue {
|
||||
host, port, err := ParseCanonicalTarget(target)
|
||||
if err != nil || (host == "" && port < 0) {
|
||||
if err != nil {
|
||||
global.Debug("failed to parse target", "target", target, "error", err)
|
||||
}
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use append so the slice is exactly allocated.
|
||||
|
||||
if port < 0 {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
semconv.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
semconv.ServerAddress(host),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
// ExportSpans instruments the ExportSpans method of the exporter. It returns
|
||||
// an [ExportOp] that must have its [ExportOp.End] method called when the
|
||||
// ExportSpans method returns.
|
||||
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
|
||||
start := time.Now()
|
||||
|
||||
if i.inflightSpans.Enabled(ctx) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, i.addOpt)
|
||||
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
|
||||
}
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
nSpans: int64(nSpans),
|
||||
inst: i,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp tracks the operation being observed by [Instrumentation.ExportSpans].
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
nSpans int64
|
||||
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End completes the observation of the operation being observed by a call to
|
||||
// [Instrumentation.ExportSpans].
|
||||
//
|
||||
// Any error that is encountered is provided as err.
|
||||
//
|
||||
// If err is not nil, all spans will be recorded as failures unless error is of
|
||||
// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number
|
||||
// of successfully exported spans will be determined by inspecting the
|
||||
// RejectedItems field of the PartialSuccess.
|
||||
func (e ExportOp) End(err error, code codes.Code) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.addOpt)
|
||||
|
||||
if e.inst.inflightSpans.Enabled(e.ctx) {
|
||||
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
|
||||
}
|
||||
|
||||
success := successful(e.nSpans, err)
|
||||
// Record successfully exported spans, even if the value is 0 which are
|
||||
// meaningful to distribution aggregations.
|
||||
if e.inst.exportedSpans.Enabled(e.ctx) {
|
||||
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
|
||||
}
|
||||
|
||||
if err != nil && e.inst.exportedSpans.Enabled(e.ctx) {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using
|
||||
// WithAttributes instead of WithAttributeSet.
|
||||
o := metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], o)
|
||||
|
||||
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
|
||||
}
|
||||
|
||||
if e.inst.opDuration.Enabled(e.ctx) {
|
||||
recOpt := get[metric.RecordOption](recordOptPool)
|
||||
defer put(recordOptPool, recOpt)
|
||||
*recOpt = append(*recOpt, e.inst.recordOption(err, code))
|
||||
|
||||
d := time.Since(e.start).Seconds()
|
||||
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
|
||||
}
|
||||
}
|
||||
|
||||
// recordOption returns a RecordOption with attributes representing the
|
||||
// outcome of the operation being recorded.
|
||||
//
|
||||
// If err is nil and code is codes.OK, the default recOpt of the
|
||||
// Instrumentation is returned.
|
||||
//
|
||||
// If err is not nil or code is not codes.OK, a new RecordOption is returned
|
||||
// with the base attributes of the Instrumentation plus the rpc.grpc.status_code
|
||||
// attribute set to the provided code, and if err is not nil, the error.type
|
||||
// attribute set to the type of the error.
|
||||
func (i *Instrumentation) recordOption(err error, code codes.Code) metric.RecordOption {
|
||||
if err == nil && code == codes.OK {
|
||||
return i.recOpt
|
||||
}
|
||||
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, i.attrs...)
|
||||
|
||||
*attrs = append(*attrs, semconv.RPCResponseStatusCode(code.String()))
|
||||
if err != nil {
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
}
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using WithAttributes
|
||||
// instead of WithAttributeSet.
|
||||
return metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
}
|
||||
|
||||
// successful returns the number of successfully exported spans out of the n
|
||||
// that were exported based on the provided error.
|
||||
//
|
||||
// If err is nil, n is returned. All spans were successfully exported.
|
||||
//
|
||||
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
|
||||
// It is assumed all spans failed to be exported.
|
||||
//
|
||||
// If err is an [internal.PartialSuccess] error, the number of successfully
|
||||
// exported spans is computed by subtracting the RejectedItems field from n. If
|
||||
// RejectedItems is negative, n is returned. If RejectedItems is greater than
|
||||
// n, 0 is returned.
|
||||
func successful(n int64, err error) int64 {
|
||||
if err == nil {
|
||||
return n // All spans successfully exported.
|
||||
}
|
||||
// Split rejection calculation so successful is inlinable.
|
||||
return n - rejected(n, err)
|
||||
}
|
||||
|
||||
var errPartialPool = &sync.Pool{
|
||||
New: func() any { return new(internal.PartialSuccess) },
|
||||
}
|
||||
|
||||
// rejected returns how many out of the n spans exporter were rejected based on
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPartialPool.Get().(*internal.PartialSuccess)
|
||||
defer errPartialPool.Put(ps)
|
||||
// Check for partial success.
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
// but be defensive as this is from an external source.
|
||||
return min(max(ps.RejectedItems, 0), n)
|
||||
}
|
||||
return n // All spans rejected.
|
||||
}
|
||||
Generated
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/observ/target.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeUnix = "unix"
|
||||
schemeUnixAbstract = "unix-abstract"
|
||||
)
|
||||
|
||||
// ParseCanonicalTarget parses a target string and returns the extracted host
|
||||
// (domain address or IP), the target port, or an error.
|
||||
//
|
||||
// If no port is specified, -1 is returned.
|
||||
//
|
||||
// If no host is specified, an empty string is returned.
|
||||
//
|
||||
// The target string is expected to always have the form
|
||||
// "<scheme>://[authority]/<endpoint>". For example:
|
||||
// - "dns:///example.com:42"
|
||||
// - "dns://8.8.8.8/example.com:42"
|
||||
// - "unix:///path/to/socket"
|
||||
// - "unix-abstract:///socket-name"
|
||||
// - "passthrough:///192.34.2.1:42"
|
||||
//
|
||||
// The target is expected to come from the CanonicalTarget method of a gRPC
|
||||
// Client.
|
||||
func ParseCanonicalTarget(target string) (string, int, error) {
|
||||
const sep = "://"
|
||||
|
||||
// Find scheme. Do not allocate the string by using url.Parse.
|
||||
idx := strings.Index(target, sep)
|
||||
if idx == -1 {
|
||||
return "", -1, fmt.Errorf("invalid target %q: missing scheme", target)
|
||||
}
|
||||
scheme, endpoint := target[:idx], target[idx+len(sep):]
|
||||
|
||||
// Check for unix schemes.
|
||||
if scheme == schemeUnix || scheme == schemeUnixAbstract {
|
||||
return parseUnix(endpoint)
|
||||
}
|
||||
|
||||
// Strip leading slash and any authority.
|
||||
if i := strings.Index(endpoint, "/"); i != -1 {
|
||||
endpoint = endpoint[i+1:]
|
||||
}
|
||||
|
||||
// DNS, passthrough, and custom resolvers.
|
||||
return parseEndpoint(endpoint)
|
||||
}
|
||||
|
||||
// parseUnix parses unix socket targets.
|
||||
func parseUnix(endpoint string) (string, int, error) {
|
||||
// Format: unix[-abstract]://path
|
||||
//
|
||||
// We should have "/path" (empty authority) if valid.
|
||||
if len(endpoint) >= 1 && endpoint[0] == '/' {
|
||||
// Return the full path including leading slash.
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
// If there's no leading slash, it means there might be an authority
|
||||
// Check for authority case (should error): "authority/path"
|
||||
if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 {
|
||||
return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx])
|
||||
}
|
||||
|
||||
return "", -1, errors.New("invalid unix target format")
|
||||
}
|
||||
|
||||
// parseEndpoint parses an endpoint from a gRPC target.
|
||||
//
|
||||
// It supports the following formats:
|
||||
// - "host"
|
||||
// - "host%zone"
|
||||
// - "host:port"
|
||||
// - "host%zone:port"
|
||||
// - "ipv4"
|
||||
// - "ipv4%zone"
|
||||
// - "ipv4:port"
|
||||
// - "ipv4%zone:port"
|
||||
// - "ipv6"
|
||||
// - "ipv6%zone"
|
||||
// - "[ipv6]"
|
||||
// - "[ipv6%zone]"
|
||||
// - "[ipv6]:port"
|
||||
// - "[ipv6%zone]:port"
|
||||
//
|
||||
// It returns the host or host%zone (domain address or IP), the port (or -1 if
|
||||
// not specified), or an error if the input is not a valid.
|
||||
func parseEndpoint(endpoint string) (string, int, error) {
|
||||
// First check if the endpoint is just an IP address.
|
||||
if ip := parseIP(endpoint); ip != "" {
|
||||
return ip, -1, nil
|
||||
}
|
||||
|
||||
// If there's no colon, there is no port (IPv6 with no port checked above).
|
||||
if !strings.Contains(endpoint, ":") {
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid host:port %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
port16, err := strconv.ParseUint(portStr, base, bitSize)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
port := int(port16) // port is guaranteed to be in the range [0, 65535].
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
// parseIP attempts to parse the entire endpoint as an IP address.
|
||||
// It returns the normalized string form of the IP if successful,
|
||||
// or an empty string if parsing fails.
|
||||
func parseIP(ip string) string {
|
||||
// Strip leading and trailing brackets for IPv6 addresses.
|
||||
if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Return the normalized string form of the IP.
|
||||
return addr.String()
|
||||
}
|
||||
Generated
Vendored
+3
-4
@@ -92,12 +92,11 @@ func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// cleanPath returns a path with all spaces trimmed and all redundancies
|
||||
// removed. If urlPath is empty or cleaning it results in an empty string,
|
||||
// cleanPath returns a path with all spaces trimmed. If urlPath is empty,
|
||||
// defaultPath is returned instead.
|
||||
func cleanPath(urlPath string, defaultPath string) string {
|
||||
tmp := path.Clean(strings.TrimSpace(urlPath))
|
||||
if tmp == "." {
|
||||
tmp := strings.TrimSpace(urlPath)
|
||||
if tmp == "" || tmp == "." {
|
||||
return defaultPath
|
||||
}
|
||||
if !path.IsAbs(tmp) {
|
||||
|
||||
Generated
Vendored
+11
@@ -29,6 +29,17 @@ func (ps PartialSuccess) Error() string {
|
||||
return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind)
|
||||
}
|
||||
|
||||
// As returns true if ps can be assigned to target and makes the assignment.
|
||||
// Otherwise, it returns false. This supports the errors.As() interface.
|
||||
func (ps PartialSuccess) As(target any) bool {
|
||||
t, ok := target.(*PartialSuccess)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
*t = ps
|
||||
return true
|
||||
}
|
||||
|
||||
// Is supports the errors.Is() interface.
|
||||
func (ps PartialSuccess) Is(err error) bool {
|
||||
_, ok := err.(PartialSuccess)
|
||||
|
||||
Generated
Vendored
+6
-1
@@ -94,6 +94,11 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if context is canceled before attempting to wait and retry.
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("%w: %w", ctx.Err(), err)
|
||||
}
|
||||
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
@@ -132,7 +137,7 @@ func wait(ctx context.Context, delay time.Duration) error {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
return ctx.Err()
|
||||
return context.Cause(ctx)
|
||||
}
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
|
||||
// exporter in use.
|
||||
const Version = "1.41.0"
|
||||
Generated
Vendored
+12
-11
@@ -1,33 +1,34 @@
|
||||
# Experimental Features
|
||||
|
||||
The Trace SDK contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the OpenTelemetry Go Trace SDK prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
The `otlptracegrpc` exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the `otlptracegrpc` exporter prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
|
||||
These features may change in backwards incompatible ways as feedback is applied.
|
||||
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
|
||||
|
||||
- [Self-Observability](#self-observability)
|
||||
- [Observability](#observability)
|
||||
|
||||
### Self-Observability
|
||||
### Observability
|
||||
|
||||
The SDK provides a self-observability feature that allows you to monitor the SDK itself.
|
||||
The `otlptracegrpc` exporter provides a observability feature that allows you to monitor the SDK itself.
|
||||
|
||||
To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`.
|
||||
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
|
||||
|
||||
When enabled, the SDK will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
- `otel.sdk.span.live`
|
||||
- `otel.sdk.span.started`
|
||||
- `otel.sdk.exporter.span.inflight`
|
||||
- `otel.sdk.exporter.span.exported`
|
||||
- `otel.sdk.exporter.operation.duration`
|
||||
|
||||
Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics.
|
||||
|
||||
[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/otel/sdk-metrics.md
|
||||
[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.37.0/docs/otel/sdk-metrics.md
|
||||
|
||||
## Compatibility and Stability
|
||||
|
||||
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md).
|
||||
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.
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that determines if exporter
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+20
-25
@@ -1,45 +1,38 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/sdk/trace].
|
||||
package x // import "go.opentelemetry.io/otel/sdk/trace/internal/x"
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SelfObservability is an experimental feature flag that determines if SDK
|
||||
// self-observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
|
||||
// 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
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
key: envKeyRoot + suffix,
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the environment variable key that needs to be set to enable the
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Key() string { return f.key }
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// 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
|
||||
@@ -49,11 +42,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
//
|
||||
// > 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
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return f.parse(vRaw)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
+1
-1
@@ -5,5 +5,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
|
||||
func Version() string {
|
||||
return "1.36.0"
|
||||
return "1.41.0"
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package errorhandler provides the global error handler for OpenTelemetry.
|
||||
//
|
||||
// This package has no OTel dependencies, allowing it to be imported by any
|
||||
// package in the module without creating import cycles.
|
||||
package errorhandler // import "go.opentelemetry.io/otel/internal/errorhandler"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// ErrorHandler handles irremediable events.
|
||||
type ErrorHandler interface {
|
||||
// Handle handles any error deemed irremediable by an OpenTelemetry
|
||||
// component.
|
||||
Handle(error)
|
||||
}
|
||||
|
||||
type ErrDelegator struct {
|
||||
delegate atomic.Pointer[ErrorHandler]
|
||||
}
|
||||
|
||||
// Compile-time check that delegator implements ErrorHandler.
|
||||
var _ ErrorHandler = (*ErrDelegator)(nil)
|
||||
|
||||
func (d *ErrDelegator) Handle(err error) {
|
||||
if eh := d.delegate.Load(); eh != nil {
|
||||
(*eh).Handle(err)
|
||||
return
|
||||
}
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
// setDelegate sets the ErrorHandler delegate.
|
||||
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
|
||||
d.delegate.Store(&eh)
|
||||
}
|
||||
|
||||
type errorHandlerHolder struct {
|
||||
eh ErrorHandler
|
||||
}
|
||||
|
||||
var (
|
||||
globalErrorHandler = defaultErrorHandler()
|
||||
delegateErrorHandlerOnce sync.Once
|
||||
)
|
||||
|
||||
// GetErrorHandler returns the global ErrorHandler instance.
|
||||
//
|
||||
// The default ErrorHandler instance returned will log all errors to STDERR
|
||||
// until an override ErrorHandler is set with SetErrorHandler. All
|
||||
// ErrorHandler returned prior to this will automatically forward errors to
|
||||
// the set instance instead of logging.
|
||||
//
|
||||
// Subsequent calls to SetErrorHandler after the first will not forward errors
|
||||
// to the new ErrorHandler for prior returned instances.
|
||||
func GetErrorHandler() ErrorHandler {
|
||||
return globalErrorHandler.Load().(errorHandlerHolder).eh
|
||||
}
|
||||
|
||||
// SetErrorHandler sets the global ErrorHandler to h.
|
||||
//
|
||||
// The first time this is called all ErrorHandler previously returned from
|
||||
// GetErrorHandler will send errors to h instead of the default logging
|
||||
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
|
||||
// delegate errors to h.
|
||||
func SetErrorHandler(h ErrorHandler) {
|
||||
current := GetErrorHandler()
|
||||
|
||||
if _, cOk := current.(*ErrDelegator); cOk {
|
||||
if _, ehOk := h.(*ErrDelegator); ehOk && current == h {
|
||||
// Do not assign to the delegate of the default ErrDelegator to be
|
||||
// itself.
|
||||
log.Print(errors.New("no ErrorHandler delegate configured"), " ErrorHandler remains its current value.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
delegateErrorHandlerOnce.Do(func() {
|
||||
if def, ok := current.(*ErrDelegator); ok {
|
||||
def.setDelegate(h)
|
||||
}
|
||||
})
|
||||
globalErrorHandler.Store(errorHandlerHolder{eh: h})
|
||||
}
|
||||
|
||||
func defaultErrorHandler() *atomic.Value {
|
||||
v := &atomic.Value{}
|
||||
v.Store(errorHandlerHolder{eh: &ErrDelegator{}})
|
||||
return v
|
||||
}
|
||||
+7
-27
@@ -5,33 +5,13 @@
|
||||
package global // import "go.opentelemetry.io/otel/internal/global"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync/atomic"
|
||||
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||
)
|
||||
|
||||
// ErrorHandler handles irremediable events.
|
||||
type ErrorHandler interface {
|
||||
// Handle handles any error deemed irremediable by an OpenTelemetry
|
||||
// component.
|
||||
Handle(error)
|
||||
}
|
||||
// ErrorHandler is an alias for errorhandler.ErrorHandler, kept for backward
|
||||
// compatibility with existing callers of internal/global.
|
||||
type ErrorHandler = errorhandler.ErrorHandler
|
||||
|
||||
type ErrDelegator struct {
|
||||
delegate atomic.Pointer[ErrorHandler]
|
||||
}
|
||||
|
||||
// Compile-time check that delegator implements ErrorHandler.
|
||||
var _ ErrorHandler = (*ErrDelegator)(nil)
|
||||
|
||||
func (d *ErrDelegator) Handle(err error) {
|
||||
if eh := d.delegate.Load(); eh != nil {
|
||||
(*eh).Handle(err)
|
||||
return
|
||||
}
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
// setDelegate sets the ErrorHandler delegate.
|
||||
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
|
||||
d.delegate.Store(&eh)
|
||||
}
|
||||
// ErrDelegator is an alias for errorhandler.ErrDelegator, kept for backward
|
||||
// compatibility with existing callers of internal/global.
|
||||
type ErrDelegator = errorhandler.ErrDelegator
|
||||
|
||||
+3
-33
@@ -8,16 +8,13 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type (
|
||||
errorHandlerHolder struct {
|
||||
eh ErrorHandler
|
||||
}
|
||||
|
||||
tracerProviderHolder struct {
|
||||
tp trace.TracerProvider
|
||||
}
|
||||
@@ -32,12 +29,10 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
globalErrorHandler = defaultErrorHandler()
|
||||
globalTracer = defaultTracerValue()
|
||||
globalPropagators = defaultPropagatorsValue()
|
||||
globalMeterProvider = defaultMeterProvider()
|
||||
|
||||
delegateErrorHandlerOnce sync.Once
|
||||
delegateTraceOnce sync.Once
|
||||
delegateTextMapPropagatorOnce sync.Once
|
||||
delegateMeterOnce sync.Once
|
||||
@@ -53,7 +48,7 @@ var (
|
||||
// Subsequent calls to SetErrorHandler after the first will not forward errors
|
||||
// to the new ErrorHandler for prior returned instances.
|
||||
func GetErrorHandler() ErrorHandler {
|
||||
return globalErrorHandler.Load().(errorHandlerHolder).eh
|
||||
return errorhandler.GetErrorHandler()
|
||||
}
|
||||
|
||||
// SetErrorHandler sets the global ErrorHandler to h.
|
||||
@@ -63,26 +58,7 @@ func GetErrorHandler() ErrorHandler {
|
||||
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
|
||||
// delegate errors to h.
|
||||
func SetErrorHandler(h ErrorHandler) {
|
||||
current := GetErrorHandler()
|
||||
|
||||
if _, cOk := current.(*ErrDelegator); cOk {
|
||||
if _, ehOk := h.(*ErrDelegator); ehOk && current == h {
|
||||
// Do not assign to the delegate of the default ErrDelegator to be
|
||||
// itself.
|
||||
Error(
|
||||
errors.New("no ErrorHandler delegate configured"),
|
||||
"ErrorHandler remains its current value.",
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
delegateErrorHandlerOnce.Do(func() {
|
||||
if def, ok := current.(*ErrDelegator); ok {
|
||||
def.setDelegate(h)
|
||||
}
|
||||
})
|
||||
globalErrorHandler.Store(errorHandlerHolder{eh: h})
|
||||
errorhandler.SetErrorHandler(h)
|
||||
}
|
||||
|
||||
// TracerProvider is the internal implementation for global.TracerProvider.
|
||||
@@ -174,12 +150,6 @@ func SetMeterProvider(mp metric.MeterProvider) {
|
||||
globalMeterProvider.Store(meterProviderHolder{mp: mp})
|
||||
}
|
||||
|
||||
func defaultErrorHandler() *atomic.Value {
|
||||
v := &atomic.Value{}
|
||||
v.Store(errorHandlerHolder{eh: &ErrDelegator{}})
|
||||
return v
|
||||
}
|
||||
|
||||
func defaultTracerValue() *atomic.Value {
|
||||
v := &atomic.Value{}
|
||||
v.Store(tracerProviderHolder{tp: &tracerProvider{}})
|
||||
|
||||
+3
@@ -211,6 +211,9 @@ type Float64Observer interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Observe(value float64, options ...ObserveOption)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -210,6 +210,9 @@ type Int64Observer interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Observe(value int64, options ...ObserveOption)
|
||||
}
|
||||
|
||||
|
||||
+53
-1
@@ -30,6 +30,9 @@ type MeterProvider interface {
|
||||
//
|
||||
// If the name is empty, then an implementation defined default name will
|
||||
// be used instead.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Meter(name string, opts ...MeterOption) Meter
|
||||
}
|
||||
|
||||
@@ -51,6 +54,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64Counter(name string, options ...Int64CounterOption) (Int64Counter, error)
|
||||
|
||||
// Int64UpDownCounter returns a new Int64UpDownCounter instrument
|
||||
@@ -61,6 +67,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64UpDownCounter(name string, options ...Int64UpDownCounterOption) (Int64UpDownCounter, error)
|
||||
|
||||
// Int64Histogram returns a new Int64Histogram instrument identified by
|
||||
@@ -71,6 +80,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64Histogram(name string, options ...Int64HistogramOption) (Int64Histogram, error)
|
||||
|
||||
// Int64Gauge returns a new Int64Gauge instrument identified by name and
|
||||
@@ -80,6 +92,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64Gauge(name string, options ...Int64GaugeOption) (Int64Gauge, error)
|
||||
|
||||
// Int64ObservableCounter returns a new Int64ObservableCounter identified
|
||||
@@ -95,6 +110,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64ObservableCounter(name string, options ...Int64ObservableCounterOption) (Int64ObservableCounter, error)
|
||||
|
||||
// Int64ObservableUpDownCounter returns a new Int64ObservableUpDownCounter
|
||||
@@ -110,6 +128,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64ObservableUpDownCounter(
|
||||
name string,
|
||||
options ...Int64ObservableUpDownCounterOption,
|
||||
@@ -128,6 +149,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Int64ObservableGauge(name string, options ...Int64ObservableGaugeOption) (Int64ObservableGauge, error)
|
||||
|
||||
// Float64Counter returns a new Float64Counter instrument identified by
|
||||
@@ -148,6 +172,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Float64UpDownCounter(name string, options ...Float64UpDownCounterOption) (Float64UpDownCounter, error)
|
||||
|
||||
// Float64Histogram returns a new Float64Histogram instrument identified by
|
||||
@@ -158,6 +185,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Float64Histogram(name string, options ...Float64HistogramOption) (Float64Histogram, error)
|
||||
|
||||
// Float64Gauge returns a new Float64Gauge instrument identified by name and
|
||||
@@ -167,6 +197,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Float64Gauge(name string, options ...Float64GaugeOption) (Float64Gauge, error)
|
||||
|
||||
// Float64ObservableCounter returns a new Float64ObservableCounter
|
||||
@@ -182,6 +215,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Float64ObservableCounter(name string, options ...Float64ObservableCounterOption) (Float64ObservableCounter, error)
|
||||
|
||||
// Float64ObservableUpDownCounter returns a new
|
||||
@@ -197,6 +233,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Float64ObservableUpDownCounter(
|
||||
name string,
|
||||
options ...Float64ObservableUpDownCounterOption,
|
||||
@@ -215,6 +254,9 @@ type Meter interface {
|
||||
// The name needs to conform to the OpenTelemetry instrument name syntax.
|
||||
// See the Instrument Name section of the package documentation for more
|
||||
// information.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Float64ObservableGauge(name string, options ...Float64ObservableGaugeOption) (Float64ObservableGauge, error)
|
||||
|
||||
// RegisterCallback registers f to be called during the collection of a
|
||||
@@ -229,6 +271,9 @@ type Meter interface {
|
||||
// If no instruments are passed, f should not be registered nor called
|
||||
// during collection.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
//
|
||||
// The function f needs to be concurrent safe.
|
||||
RegisterCallback(f Callback, instruments ...Observable) (Registration, error)
|
||||
}
|
||||
@@ -263,9 +308,15 @@ type Observer interface {
|
||||
embedded.Observer
|
||||
|
||||
// ObserveFloat64 records the float64 value for obsrv.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
ObserveFloat64(obsrv Float64Observable, value float64, opts ...ObserveOption)
|
||||
|
||||
// ObserveInt64 records the int64 value for obsrv.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
ObserveInt64(obsrv Int64Observable, value int64, opts ...ObserveOption)
|
||||
}
|
||||
|
||||
@@ -283,6 +334,7 @@ type Registration interface {
|
||||
|
||||
// Unregister removes the callback registration from a Meter.
|
||||
//
|
||||
// This method needs to be idempotent and concurrent safe.
|
||||
// Implementations of this method need to be idempotent and safe for a user
|
||||
// to call concurrently.
|
||||
Unregister() error
|
||||
}
|
||||
|
||||
+24
@@ -24,12 +24,18 @@ type Float64Counter interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Add(ctx context.Context, incr float64, options ...AddOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
@@ -83,12 +89,18 @@ type Float64UpDownCounter interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Add(ctx context.Context, incr float64, options ...AddOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
@@ -142,12 +154,18 @@ type Float64Histogram interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Record(ctx context.Context, incr float64, options ...RecordOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
@@ -206,12 +224,18 @@ type Float64Gauge interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Record(ctx context.Context, value float64, options ...RecordOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
|
||||
+24
@@ -24,12 +24,18 @@ type Int64Counter interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Add(ctx context.Context, incr int64, options ...AddOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
@@ -83,12 +89,18 @@ type Int64UpDownCounter interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Add(ctx context.Context, incr int64, options ...AddOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
@@ -142,12 +154,18 @@ type Int64Histogram interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Record(ctx context.Context, incr int64, options ...RecordOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
@@ -206,12 +224,18 @@ type Int64Gauge interface {
|
||||
//
|
||||
// Use the WithAttributeSet (or, if performance is not a concern,
|
||||
// the WithAttributes) option to include measurement attributes.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Record(ctx context.Context, value int64, options ...RecordOption)
|
||||
|
||||
// Enabled reports whether the instrument will process measurements for the given context.
|
||||
//
|
||||
// This function can be used in places where measuring an instrument
|
||||
// would result in computationally expensive operations.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(context.Context) bool
|
||||
}
|
||||
|
||||
|
||||
+22
-2
@@ -7,9 +7,16 @@ import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/baggage"
|
||||
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||
)
|
||||
|
||||
const baggageHeader = "baggage"
|
||||
const (
|
||||
baggageHeader = "baggage"
|
||||
|
||||
// W3C Baggage specification limits.
|
||||
// https://www.w3.org/TR/baggage/#limits
|
||||
maxMembers = 64
|
||||
)
|
||||
|
||||
// Baggage is a propagator that supports the W3C Baggage format.
|
||||
//
|
||||
@@ -50,6 +57,9 @@ func extractSingleBaggage(parent context.Context, carrier TextMapCarrier) contex
|
||||
|
||||
bag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
}
|
||||
if bag.Len() == 0 {
|
||||
return parent
|
||||
}
|
||||
return baggage.ContextWithBaggage(parent, bag)
|
||||
@@ -60,17 +70,27 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
|
||||
if len(bVals) == 0 {
|
||||
return parent
|
||||
}
|
||||
|
||||
var members []baggage.Member
|
||||
for _, bStr := range bVals {
|
||||
currBag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
}
|
||||
if currBag.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
members = append(members, currBag.Members()...)
|
||||
if len(members) >= maxMembers {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b, err := baggage.New(members...)
|
||||
if err != nil || b.Len() == 0 {
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
}
|
||||
if b.Len() == 0 {
|
||||
return parent
|
||||
}
|
||||
return baggage.ContextWithBaggage(parent, b)
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/sdk].
|
||||
package x // import "go.opentelemetry.io/otel/sdk/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Resource is an experimental feature flag that defines if resource detectors
|
||||
// should be included experimental semantic conventions.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_RESOURCE environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Resource = newFeature(
|
||||
[]string{"RESOURCE"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
|
||||
// Observability is an experimental feature flag that determines if SDK
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY", "SELF_OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
+19
-27
@@ -1,48 +1,38 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x contains support for OTel 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 documents experimental features for [go.opentelemetry.io/otel/sdk].
|
||||
package x // import "go.opentelemetry.io/otel/sdk/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Resource is an experimental feature flag that defines if resource detectors
|
||||
// should be included experimental semantic conventions.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_RESOURCE environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Resource = newFeature("RESOURCE", func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
|
||||
// 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
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
key: envKeyRoot + suffix,
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the environment variable key that needs to be set to enable the
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Key() string { return f.key }
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// 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
|
||||
@@ -52,11 +42,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
//
|
||||
// > 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
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return f.parse(vRaw)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
)
|
||||
|
||||
type containerIDProvider func() (string, error)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user