1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00
Files
Tyler Yahn 6cb0e90c0e Generate gRPC Client target parsing func (#7424)
In order to add [gRPC server attributes for exporter
observability](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/otel/sdk-metrics.md#metric-otelsdkexporterspaninflight),
this information needs to be parsed into a host and port. The added
generated files provides `ParseCanonicalTarget` for this functionality.

This is added as a generated template as it is expected to be needed for
all OTLP exporters (e.g. #7404, #7353).

Split from work added to #7404 

cc @yumosx 

### Benchamarks

```
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
                                 │ otlptracegrpc-observ-target.bmark.result │
                                 │                  sec/op                  │
ParseTarget/HostName-8                                         80.90n ±  1%
ParseTarget/HostPort-8                                         123.2n ±  4%
ParseTarget/IPv4WithoutPort-8                                  94.25n ±  2%
ParseTarget/IPv4WithPort-8                                     136.2n ±  1%
ParseTarget/IPv6Bare-8                                         195.5n ±  2%
ParseTarget/IPv6Bracket-8                                      191.2n ±  3%
ParseTarget/IPv6WithPort-8                                     128.6n ±  4%
ParseTarget/UnixSocket-8                                       15.73n ±  4%
ParseTarget/UnixAbstractSocket-8                               15.71n ±  6%
ParseTarget/Passthrough-8                                      129.3n ± 18%
geomean                                                        84.98n

                                 │ otlptracegrpc-observ-target.bmark.result │
                                 │                   B/op                   │
ParseTarget/HostName-8                                         48.00 ± 0%
ParseTarget/HostPort-8                                         48.00 ± 0%
ParseTarget/IPv4WithoutPort-8                                  16.00 ± 0%
ParseTarget/IPv4WithPort-8                                     48.00 ± 0%
ParseTarget/IPv6Bare-8                                         16.00 ± 0%
ParseTarget/IPv6Bracket-8                                      16.00 ± 0%
ParseTarget/IPv6WithPort-8                                     48.00 ± 0%
ParseTarget/UnixSocket-8                                       0.000 ± 0%
ParseTarget/UnixAbstractSocket-8                               0.000 ± 0%
ParseTarget/Passthrough-8                                      48.00 ± 0%
geomean                                                                   ¹
¹ summaries must be >0 to compute geomean

                                 │ otlptracegrpc-observ-target.bmark.result │
                                 │                allocs/op                 │
ParseTarget/HostName-8                                         1.000 ± 0%
ParseTarget/HostPort-8                                         1.000 ± 0%
ParseTarget/IPv4WithoutPort-8                                  1.000 ± 0%
ParseTarget/IPv4WithPort-8                                     1.000 ± 0%
ParseTarget/IPv6Bare-8                                         1.000 ± 0%
ParseTarget/IPv6Bracket-8                                      1.000 ± 0%
ParseTarget/IPv6WithPort-8                                     1.000 ± 0%
ParseTarget/UnixSocket-8                                       0.000 ± 0%
ParseTarget/UnixAbstractSocket-8                               0.000 ± 0%
ParseTarget/Passthrough-8                                      1.000 ± 0%
geomean                                                                   ¹
¹ summaries must be >0 to compute geomean
```
2025-09-29 07:33:08 -07:00

144 lines
4.0 KiB
Cheetah

// 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 {{ .pkg }} // import "{{ .pkg_path }}"
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()
}