Han Joker 2 years ago
parent 8b6b340e3c
commit 9cb7660b65

1
.gitignore vendored

@ -0,0 +1 @@
hotrod-linux

@ -0,0 +1,6 @@
FROM scratch
ARG TARGETARCH
EXPOSE 8080 8081 8082 8083
COPY hotrod-linux-$TARGETARCH /go/bin/hotrod-linux
ENTRYPOINT ["/go/bin/hotrod-linux"]
CMD ["all"]

@ -0,0 +1,38 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"github.com/spf13/cobra"
)
// allCmd represents the all command
var allCmd = &cobra.Command{
Use: "all",
Short: "Starts all services",
Long: `Starts all services.`,
RunE: func(cmd *cobra.Command, args []string) error {
logger.Info("Starting all services")
go customerCmd.RunE(customerCmd, args)
go driverCmd.RunE(driverCmd, args)
go routeCmd.RunE(routeCmd, args)
return frontendCmd.RunE(frontendCmd, args)
},
}
func init() {
RootCmd.AddCommand(allCmd)
}

@ -0,0 +1,49 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"net"
"strconv"
"github.com/spf13/cobra"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/services/customer"
)
// customerCmd represents the customer command
var customerCmd = &cobra.Command{
Use: "customer",
Short: "Starts Customer service",
Long: `Starts Customer service.`,
RunE: func(cmd *cobra.Command, args []string) error {
zapLogger := logger.With(zap.String("service", "customer"))
logger := log.NewFactory(zapLogger)
server := customer.NewServer(
net.JoinHostPort("0.0.0.0", strconv.Itoa(customerPort)),
otelExporter,
metricsFactory,
logger,
)
return logError(zapLogger, server.Run())
},
}
func init() {
RootCmd.AddCommand(customerCmd)
}

@ -0,0 +1,49 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"net"
"strconv"
"github.com/spf13/cobra"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/services/driver"
)
// driverCmd represents the driver command
var driverCmd = &cobra.Command{
Use: "driver",
Short: "Starts Driver service",
Long: `Starts Driver service.`,
RunE: func(cmd *cobra.Command, args []string) error {
zapLogger := logger.With(zap.String("service", "driver"))
logger := log.NewFactory(zapLogger)
server := driver.NewServer(
net.JoinHostPort("0.0.0.0", strconv.Itoa(driverPort)),
otelExporter,
metricsFactory,
logger,
)
return logError(zapLogger, server.Run())
},
}
func init() {
RootCmd.AddCommand(driverCmd)
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,63 @@
// Copyright (c) 2023 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"time"
"github.com/spf13/cobra"
)
var (
metricsBackend string
otelExporter string // otlp, stdout
verbose bool
fixDBConnDelay time.Duration
fixDBConnDisableMutex bool
fixRouteWorkerPoolSize int
customerPort int
driverPort int
frontendPort int
routePort int
basepath string
jaegerUI string
)
const expvarDepr = "(deprecated, will be removed after 2024-01-01 or in release v1.53.0, whichever is later) "
// used by root command
func addFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&metricsBackend, "metrics", "m", "prometheus", expvarDepr+"Metrics backend (expvar|prometheus). ")
cmd.PersistentFlags().StringVarP(&otelExporter, "otel-exporter", "x", "otlp", "OpenTelemetry exporter (otlp|stdout)")
cmd.PersistentFlags().DurationVarP(&fixDBConnDelay, "fix-db-query-delay", "D", 300*time.Millisecond, "Average latency of MySQL DB query")
cmd.PersistentFlags().BoolVarP(&fixDBConnDisableMutex, "fix-disable-db-conn-mutex", "M", false, "Disables the mutex guarding db connection")
cmd.PersistentFlags().IntVarP(&fixRouteWorkerPoolSize, "fix-route-worker-pool-size", "W", 3, "Default worker pool size")
// Add flags to choose ports for services
cmd.PersistentFlags().IntVarP(&customerPort, "customer-service-port", "c", 8081, "Port for customer service")
cmd.PersistentFlags().IntVarP(&driverPort, "driver-service-port", "d", 8082, "Port for driver service")
cmd.PersistentFlags().IntVarP(&frontendPort, "frontend-service-port", "f", 8080, "Port for frontend service")
cmd.PersistentFlags().IntVarP(&routePort, "route-service-port", "r", 8083, "Port for routing service")
// Flag for serving frontend at custom basepath url
cmd.PersistentFlags().StringVarP(&basepath, "basepath", "b", "", `Basepath for frontend service(default "/")`)
cmd.PersistentFlags().StringVarP(&jaegerUI, "jaeger-ui", "j", "http://localhost:16686", "Address of Jaeger UI to create [find trace] links")
cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enables debug logging")
}

@ -0,0 +1,58 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"net"
"strconv"
"github.com/spf13/cobra"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/examples/hotrod/services/frontend"
)
// frontendCmd represents the frontend command
var frontendCmd = &cobra.Command{
Use: "frontend",
Short: "Starts Frontend service",
Long: `Starts Frontend service.`,
RunE: func(cmd *cobra.Command, args []string) error {
options.FrontendHostPort = net.JoinHostPort("0.0.0.0", strconv.Itoa(frontendPort))
options.DriverHostPort = net.JoinHostPort("0.0.0.0", strconv.Itoa(driverPort))
options.CustomerHostPort = net.JoinHostPort("0.0.0.0", strconv.Itoa(customerPort))
options.RouteHostPort = net.JoinHostPort("0.0.0.0", strconv.Itoa(routePort))
options.Basepath = basepath
options.JaegerUI = jaegerUI
zapLogger := logger.With(zap.String("service", "frontend"))
logger := log.NewFactory(zapLogger)
server := frontend.NewServer(
options,
tracing.InitOTEL("frontend", otelExporter, metricsFactory, logger),
logger,
)
return logError(zapLogger, server.Run())
},
}
var options frontend.ConfigOptions
func init() {
RootCmd.AddCommand(frontendCmd)
}

@ -0,0 +1,122 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"os"
"github.com/spf13/cobra"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/jaegertracing/jaeger/examples/hotrod/services/config"
"github.com/jaegertracing/jaeger/internal/jaegerclientenv2otel"
"github.com/jaegertracing/jaeger/internal/metrics/expvar"
"github.com/jaegertracing/jaeger/internal/metrics/prometheus"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
var (
logger *zap.Logger
metricsFactory metrics.Factory
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "examples-hotrod",
Short: "HotR.O.D. - A tracing demo application",
Long: `HotR.O.D. - A tracing demo application.`,
}
// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
logger.Fatal("We bowled a googly", zap.Error(err))
os.Exit(-1)
}
}
func init() {
addFlags(RootCmd)
cobra.OnInitialize(onInitialize)
}
// onInitialize is called before the command is executed.
func onInitialize() {
zapOptions := []zap.Option{
zap.AddStacktrace(zapcore.FatalLevel),
zap.AddCallerSkip(1),
}
if !verbose {
zapOptions = append(zapOptions,
zap.IncreaseLevel(zap.LevelEnablerFunc(func(l zapcore.Level) bool { return l != zapcore.DebugLevel })),
)
}
logger, _ = zap.NewDevelopment(zapOptions...)
jaegerclientenv2otel.MapJaegerToOtelEnvVars(logger)
switch metricsBackend {
case "expvar":
metricsFactory = expvar.NewFactory(10) // 10 buckets for histograms
logger.Info("*** Using expvar as metrics backend " + expvarDepr)
case "prometheus":
metricsFactory = prometheus.New().Namespace(metrics.NSOptions{Name: "hotrod", Tags: nil})
logger.Info("Using Prometheus as metrics backend")
default:
logger.Fatal("unsupported metrics backend " + metricsBackend)
}
if config.MySQLGetDelay != fixDBConnDelay {
logger.Info("fix: overriding MySQL query delay", zap.Duration("old", config.MySQLGetDelay), zap.Duration("new", fixDBConnDelay))
config.MySQLGetDelay = fixDBConnDelay
}
if fixDBConnDisableMutex {
logger.Info("fix: disabling db connection mutex")
config.MySQLMutexDisabled = true
}
if config.RouteWorkerPoolSize != fixRouteWorkerPoolSize {
logger.Info("fix: overriding route worker pool size", zap.Int("old", config.RouteWorkerPoolSize), zap.Int("new", fixRouteWorkerPoolSize))
config.RouteWorkerPoolSize = fixRouteWorkerPoolSize
}
if customerPort != 8081 {
logger.Info("changing customer service port", zap.Int("old", 8081), zap.Int("new", customerPort))
}
if driverPort != 8082 {
logger.Info("changing driver service port", zap.Int("old", 8082), zap.Int("new", driverPort))
}
if frontendPort != 8080 {
logger.Info("changing frontend service port", zap.Int("old", 8080), zap.Int("new", frontendPort))
}
if routePort != 8083 {
logger.Info("changing route service port", zap.Int("old", 8083), zap.Int("new", routePort))
}
if basepath != "" {
logger.Info("changing basepath for frontend", zap.String("old", "/"), zap.String("new", basepath))
}
}
func logError(logger *zap.Logger, err error) error {
if err != nil {
logger.Error("Error running command", zap.Error(err))
}
return err
}

@ -0,0 +1,49 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"net"
"strconv"
"github.com/spf13/cobra"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/examples/hotrod/services/route"
)
// routeCmd represents the route command
var routeCmd = &cobra.Command{
Use: "route",
Short: "Starts Route service",
Long: `Starts Route service.`,
RunE: func(cmd *cobra.Command, args []string) error {
zapLogger := logger.With(zap.String("service", "route"))
logger := log.NewFactory(zapLogger)
server := route.NewServer(
net.JoinHostPort("0.0.0.0", strconv.Itoa(routePort)),
tracing.InitOTEL("route", otelExporter, metricsFactory, logger),
logger,
)
return logError(zapLogger, server.Run())
},
}
func init() {
RootCmd.AddCommand(routeCmd)
}

@ -0,0 +1,33 @@
version: '3.7'
# To run a specific version of Jaeger, use environment variable, e.g.:
# JAEGER_VERSION=1.52 docker compose up
services:
jaeger:
image: jaegertracing/all-in-one:${JAEGER_VERSION:-latest}
ports:
- "16686:16686"
- "4318:4318"
environment:
- LOG_LEVEL=debug
networks:
- jaeger-example
hotrod:
image: jaegertracing/example-hotrod:${JAEGER_VERSION:-latest}
# To run the latest trunk build, find the tag at Docker Hub and use the line below
# https://hub.docker.com/r/jaegertracing/example-hotrod-snapshot/tags
#image: jaegertracing/example-hotrod-snapshot:0ab8f2fcb12ff0d10830c1ee3bb52b745522db6c
ports:
- "8080:8080"
- "8083:8083"
command: ["all"]
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
networks:
- jaeger-example
depends_on:
- jaeger
networks:
jaeger-example:

@ -0,0 +1,53 @@
module hotrod
go 1.22.0
require (
github.com/gogo/protobuf v1.3.2
github.com/jaegertracing/jaeger v1.55.0
github.com/prometheus/client_golang v1.19.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
go.uber.org/zap v1.27.0
google.golang.org/grpc v1.62.1
)
require (
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.49.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

180
go.sum

@ -0,0 +1,180 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jaegertracing/jaeger v1.55.0 h1:IJHzKb2B9EYQyKlE7VSoKzNP3emHeqZWnWrKj+kYzzs=
github.com/jaegertracing/jaeger v1.55.0/go.mod h1:S884Mz8H+iGI8Ealq6sM9QzSOeU6P+nbFkYw7uww8CI=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI=
github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

@ -0,0 +1,17 @@
# Hot R.O.D. - Rides on Demand on Kubernetes
Example k8s manifests for deploying the [hotrod app](..) to your k8s environment of choice. e.g. minikube, k3s, EKS, GKE
## Usage
```bash
kustomize build . | kubectl apply -f -
kubectl port-forward -n example-hotrod service/example-hotrod 8080:frontend
# In another terminal
kubectl port-forward -n example-hotrod service/jaeger 16686:frontend
# To cleanup
kustomize build . | kubectl delete -f -
```
Access Jaeger UI at <http://localhost:16686> and HotROD app at <http://localhost:8080>

@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: example-hotrod
name: example-hotrod
spec:
replicas: 1
selector:
matchLabels:
app: example-hotrod
strategy: {}
template:
metadata:
labels:
app: example-hotrod
spec:
containers:
- image: jaegertracing/example-hotrod:latest
name: example-hotrod
args: ["all"]
env:
- name: JAEGER_AGENT_HOST
value: localhost
- name: JAEGER_AGENT_PORT
value: "6831"
ports:
- containerPort: 8080
name: frontend
- containerPort: 8081
name: customer
- containerPort: 8083
name: route
resources:
limits:
cpu: 100m
memory: 100M
requests:
cpu: 100m
memory: 100M

@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: example-hotrod
spec:
selector:
app: example-hotrod
ports:
- name: frontend
protocol: TCP
port: 8080
targetPort: frontend

@ -0,0 +1,33 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../hotrod
resources:
- service.yaml
patches:
- target:
group: apps
version: v1
kind: Deployment
name: example-hotrod
patch: |-
- op: add
path: /spec/template/spec/containers/-
value:
image: jaegertracing/all-in-one:latest
name: jaeger
ports:
- containerPort: 6831
name: tracing-jaeger
- containerPort: 16686
name: frontend-jaeger
resources:
limits:
cpu: 100m
memory: 100M
requests:
cpu: 100m
memory: 100M

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: jaeger
spec:
selector:
app: example-hotrod
ports:
- name: tracing
protocol: UDP
port: 6831
targetPort: tracing-jaeger
- name: frontend
protocol: TCP
port: 16686
targetPort: frontend-jaeger

@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
# If you only want the hotrod application, uncomment this line and comment out jaeger-all-in-one
# - base/hotrod
# Leaving this uncommented will install both hotrod and an example all-in-one
- base/jaeger-all-in-one
namespace: example-hotrod
resources:
- namespace.yaml

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: example-hotrod

@ -0,0 +1,12 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
package main
import (
"github.com/jaegertracing/jaeger/examples/hotrod/cmd"
)
func main() {
cmd.Execute()
}

@ -0,0 +1,31 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package delay
import (
"math"
"math/rand"
"time"
)
// Sleep generates a normally distributed random delay with given mean and stdDev
// and blocks for that duration.
func Sleep(mean time.Duration, stdDev time.Duration) {
fMean := float64(mean)
fStdDev := float64(stdDev)
delay := time.Duration(math.Max(1, rand.NormFloat64()*fStdDev+fMean))
time.Sleep(delay)
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package delay
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httperr
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,30 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httperr
import (
"net/http"
)
// HandleError checks if the error is not nil, writes it to the output
// with the specified status code, and returns true. If error is nil it returns false.
func HandleError(w http.ResponseWriter, err error, statusCode int) bool {
if err == nil {
return false
}
http.Error(w, string(err.Error()), statusCode)
return true
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,60 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"context"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Factory is the default logging wrapper that can create
// logger instances either for a given Context or context-less.
type Factory struct {
logger *zap.Logger
}
// NewFactory creates a new Factory.
func NewFactory(logger *zap.Logger) Factory {
return Factory{logger: logger}
}
// Bg creates a context-unaware logger.
func (b Factory) Bg() Logger {
return wrapper(b)
}
// For returns a context-aware Logger. If the context
// contains a span, all logging calls are also
// echo-ed into the span.
func (b Factory) For(ctx context.Context) Logger {
if span := trace.SpanFromContext(ctx); span != nil {
logger := spanLogger{span: span, logger: b.logger}
logger.spanFields = []zapcore.Field{
zap.String("trace_id", span.SpanContext().TraceID().String()),
zap.String("span_id", span.SpanContext().SpanID().String()),
}
return logger
}
return b.Bg()
}
// With creates a child logger, and optionally adds some context fields to that logger.
func (b Factory) With(fields ...zapcore.Field) Factory {
return Factory{logger: b.logger.With(fields...)}
}

@ -0,0 +1,60 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Logger is a simplified abstraction of the zap.Logger
type Logger interface {
Debug(msg string, fields ...zapcore.Field)
Info(msg string, fields ...zapcore.Field)
Error(msg string, fields ...zapcore.Field)
Fatal(msg string, fields ...zapcore.Field)
With(fields ...zapcore.Field) Logger
}
// wrapper delegates all calls to the underlying zap.Logger
type wrapper struct {
logger *zap.Logger
}
// Debug logs an debug msg with fields
func (l wrapper) Debug(msg string, fields ...zapcore.Field) {
l.logger.Debug(msg, fields...)
}
// Info logs an info msg with fields
func (l wrapper) Info(msg string, fields ...zapcore.Field) {
l.logger.Info(msg, fields...)
}
// Error logs an error msg with fields
func (l wrapper) Error(msg string, fields ...zapcore.Field) {
l.logger.Error(msg, fields...)
}
// Fatal logs a fatal error msg with fields
func (l wrapper) Fatal(msg string, fields ...zapcore.Field) {
l.logger.Fatal(msg, fields...)
}
// With creates a child logger, and optionally adds some context fields to that logger.
func (l wrapper) With(fields ...zapcore.Field) Logger {
return wrapper{logger: l.logger.With(fields...)}
}

@ -0,0 +1,176 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"fmt"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type spanLogger struct {
logger *zap.Logger
span trace.Span
spanFields []zapcore.Field
}
func (sl spanLogger) Debug(msg string, fields ...zapcore.Field) {
sl.logToSpan("debug", msg, fields...)
sl.logger.Debug(msg, append(sl.spanFields, fields...)...)
}
func (sl spanLogger) Info(msg string, fields ...zapcore.Field) {
sl.logToSpan("info", msg, fields...)
sl.logger.Info(msg, append(sl.spanFields, fields...)...)
}
func (sl spanLogger) Error(msg string, fields ...zapcore.Field) {
sl.logToSpan("error", msg, fields...)
sl.logger.Error(msg, append(sl.spanFields, fields...)...)
}
func (sl spanLogger) Fatal(msg string, fields ...zapcore.Field) {
sl.logToSpan("fatal", msg, fields...)
sl.span.SetStatus(codes.Error, msg)
sl.logger.Fatal(msg, append(sl.spanFields, fields...)...)
}
// With creates a child logger, and optionally adds some context fields to that logger.
func (sl spanLogger) With(fields ...zapcore.Field) Logger {
return spanLogger{logger: sl.logger.With(fields...), span: sl.span, spanFields: sl.spanFields}
}
func (sl spanLogger) logToSpan(level, msg string, fields ...zapcore.Field) {
fields = append(fields, zap.String("level", level))
sl.span.AddEvent(
msg,
trace.WithAttributes(logFieldsToOTelAttrs(fields)...),
)
}
func logFieldsToOTelAttrs(fields []zapcore.Field) []attribute.KeyValue {
encoder := &bridgeFieldEncoder{}
for _, field := range fields {
field.AddTo(encoder)
}
return encoder.pairs
}
type bridgeFieldEncoder struct {
pairs []attribute.KeyValue
}
func (e *bridgeFieldEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(marshaler)))
return nil
}
func (e *bridgeFieldEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(marshaler)))
return nil
}
func (e *bridgeFieldEncoder) AddBinary(key string, value []byte) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddByteString(key string, value []byte) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddBool(key string, value bool) {
e.pairs = append(e.pairs, attribute.Bool(key, value))
}
func (e *bridgeFieldEncoder) AddComplex128(key string, value complex128) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddComplex64(key string, value complex64) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddDuration(key string, value time.Duration) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddFloat64(key string, value float64) {
e.pairs = append(e.pairs, attribute.Float64(key, value))
}
func (e *bridgeFieldEncoder) AddFloat32(key string, value float32) {
e.pairs = append(e.pairs, attribute.Float64(key, float64(value)))
}
func (e *bridgeFieldEncoder) AddInt(key string, value int) {
e.pairs = append(e.pairs, attribute.Int(key, value))
}
func (e *bridgeFieldEncoder) AddInt64(key string, value int64) {
e.pairs = append(e.pairs, attribute.Int64(key, value))
}
func (e *bridgeFieldEncoder) AddInt32(key string, value int32) {
e.pairs = append(e.pairs, attribute.Int64(key, int64(value)))
}
func (e *bridgeFieldEncoder) AddInt16(key string, value int16) {
e.pairs = append(e.pairs, attribute.Int64(key, int64(value)))
}
func (e *bridgeFieldEncoder) AddInt8(key string, value int8) {
e.pairs = append(e.pairs, attribute.Int64(key, int64(value)))
}
func (e *bridgeFieldEncoder) AddString(key, value string) {
e.pairs = append(e.pairs, attribute.String(key, value))
}
func (e *bridgeFieldEncoder) AddTime(key string, value time.Time) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddUint(key string, value uint) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprintf("%d", value)))
}
func (e *bridgeFieldEncoder) AddUint64(key string, value uint64) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprintf("%d", value)))
}
func (e *bridgeFieldEncoder) AddUint32(key string, value uint32) {
e.pairs = append(e.pairs, attribute.Int64(key, int64(value)))
}
func (e *bridgeFieldEncoder) AddUint16(key string, value uint16) {
e.pairs = append(e.pairs, attribute.Int64(key, int64(value)))
}
func (e *bridgeFieldEncoder) AddUint8(key string, value uint8) {
e.pairs = append(e.pairs, attribute.Int64(key, int64(value)))
}
func (e *bridgeFieldEncoder) AddUintptr(key string, value uintptr) {
e.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))
}
func (e *bridgeFieldEncoder) AddReflected(key string, value interface{}) error { return nil }
func (e *bridgeFieldEncoder) OpenNamespace(key string) {}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pool
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,56 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pool
// Pool is a simple worker pool
type Pool struct {
jobs chan func()
stop chan struct{}
}
// New creates a new pool with the given number of workers
func New(workers int) *Pool {
jobs := make(chan func())
stop := make(chan struct{})
for i := 0; i < workers; i++ {
go func() {
for {
select {
case job := <-jobs:
job()
case <-stop:
return
}
}
}()
}
return &Pool{
jobs: jobs,
stop: stop,
}
}
// Execute enqueues the job to be executed by one of the workers in the pool
func (p *Pool) Execute(job func()) {
p.jobs <- job
}
// Stop halts all the workers
func (p *Pool) Stop() {
if p.stop != nil {
close(p.stop)
}
}

@ -0,0 +1,27 @@
// Copyright (c) 2023 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tracing
import (
"context"
"go.opentelemetry.io/otel/baggage"
)
func BaggageItem(ctx context.Context, key string) string {
b := baggage.FromContext(ctx)
m := b.Member(key)
return m.Value()
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tracing
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,72 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tracing
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/trace"
)
// HTTPClient wraps an http.Client with tracing instrumentation.
type HTTPClient struct {
TracerProvider trace.TracerProvider
Client *http.Client
}
func NewHTTPClient(tp trace.TracerProvider) *HTTPClient {
return &HTTPClient{
TracerProvider: tp,
Client: &http.Client{
Transport: otelhttp.NewTransport(
http.DefaultTransport,
otelhttp.WithTracerProvider(tp),
),
},
}
}
// GetJSON executes HTTP GET against specified url and tried to parse
// the response into out object.
func (c *HTTPClient) GetJSON(ctx context.Context, endpoint string, url string, out interface{}) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
res, err := c.Client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
return errors.New(string(body))
}
decoder := json.NewDecoder(res.Body)
return decoder.Decode(out)
}

@ -0,0 +1,111 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tracing
import (
"context"
"errors"
"fmt"
"os"
"strings"
"sync"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing/rpcmetrics"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
var once sync.Once
// InitOTEL initializes OpenTelemetry SDK.
func InitOTEL(serviceName string, exporterType string, metricsFactory metrics.Factory, logger log.Factory) trace.TracerProvider {
once.Do(func() {
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
})
exp, err := createOtelExporter(exporterType)
if err != nil {
logger.Bg().Fatal("cannot create exporter", zap.String("exporterType", exporterType), zap.Error(err))
}
logger.Bg().Debug("using " + exporterType + " trace exporter")
rpcmetricsObserver := rpcmetrics.NewObserver(metricsFactory, rpcmetrics.DefaultNameNormalizer)
res, err := resource.New(
context.Background(),
resource.WithSchemaURL(semconv.SchemaURL),
resource.WithAttributes(semconv.ServiceNameKey.String(serviceName)),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithOSType(),
)
if err != nil {
logger.Bg().Fatal("resource creation failed", zap.Error(err))
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp, sdktrace.WithBatchTimeout(1000*time.Millisecond)),
sdktrace.WithSpanProcessor(rpcmetricsObserver),
sdktrace.WithResource(res),
)
logger.Bg().Debug("Created OTEL tracer", zap.String("service-name", serviceName))
return tp
}
// withSecure instructs the client to use HTTPS scheme, instead of hotrod's desired default HTTP
func withSecure() bool {
return strings.HasPrefix(os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT"), "https://") ||
strings.ToLower(os.Getenv("OTEL_EXPORTER_OTLP_INSECURE")) == "false"
}
func createOtelExporter(exporterType string) (sdktrace.SpanExporter, error) {
var exporter sdktrace.SpanExporter
var err error
switch exporterType {
case "jaeger":
return nil, errors.New("jaeger exporter is no longer supported, please use otlp")
case "otlp":
var opts []otlptracehttp.Option
if !withSecure() {
opts = []otlptracehttp.Option{otlptracehttp.WithInsecure()}
}
exporter, err = otlptrace.New(
context.Background(),
otlptracehttp.NewClient(opts...),
)
case "stdout":
exporter, err = stdouttrace.New()
default:
return nil, fmt.Errorf("unrecognized exporter type %s", exporterType)
}
return exporter, err
}

@ -0,0 +1,86 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tracing
import (
"context"
"fmt"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
)
// Mutex is just like the standard sync.Mutex, except that it is aware of the Context
// and logs some diagnostic information into the current span.
type Mutex struct {
SessionBaggageKey string
LogFactory log.Factory
realLock sync.Mutex
holder string
waiters []string
waitersLock sync.Mutex
}
// Lock acquires an exclusive lock.
func (sm *Mutex) Lock(ctx context.Context) {
logger := sm.LogFactory.For(ctx)
session := BaggageItem(ctx, sm.SessionBaggageKey)
activeSpan := trace.SpanFromContext(ctx)
activeSpan.SetAttributes(attribute.String(sm.SessionBaggageKey, session))
sm.waitersLock.Lock()
if waiting := len(sm.waiters); waiting > 0 && activeSpan != nil {
logger.Info(
fmt.Sprintf("Waiting for lock behind %d transactions", waiting),
zap.String("blockers", fmt.Sprintf("%v", sm.waiters)),
)
}
sm.waiters = append(sm.waiters, session)
sm.waitersLock.Unlock()
sm.realLock.Lock()
sm.holder = session
sm.waitersLock.Lock()
behindLen := len(sm.waiters) - 1
behindIDs := fmt.Sprintf("%v", sm.waiters[1:]) // skip self
sm.waitersLock.Unlock()
logger.Info(
fmt.Sprintf("Acquired lock; %d transactions waiting behind", behindLen),
zap.String("waiters", behindIDs),
)
}
// Unlock releases the lock.
func (sm *Mutex) Unlock() {
sm.waitersLock.Lock()
for i, v := range sm.waiters {
if v == sm.holder {
sm.waiters = append(sm.waiters[0:i], sm.waiters[i+1:]...)
break
}
}
sm.waitersLock.Unlock()
sm.realLock.Unlock()
}

@ -0,0 +1,77 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tracing
import (
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
)
// NewServeMux creates a new TracedServeMux.
func NewServeMux(copyBaggage bool, tracer trace.TracerProvider, logger log.Factory) *TracedServeMux {
return &TracedServeMux{
mux: http.NewServeMux(),
copyBaggage: copyBaggage,
tracer: tracer,
logger: logger,
}
}
// TracedServeMux is a wrapper around http.ServeMux that instruments handlers for tracing.
type TracedServeMux struct {
mux *http.ServeMux
copyBaggage bool
tracer trace.TracerProvider
logger log.Factory
}
// Handle implements http.ServeMux#Handle, which is used to register new handler.
func (tm *TracedServeMux) Handle(pattern string, handler http.Handler) {
tm.logger.Bg().Debug("registering traced handler", zap.String("endpoint", pattern))
middleware := otelhttp.NewHandler(
otelhttp.WithRouteTag(pattern, traceResponseHandler(handler)),
pattern,
otelhttp.WithTracerProvider(tm.tracer))
tm.mux.Handle(pattern, middleware)
}
// ServeHTTP implements http.ServeMux#ServeHTTP.
func (tm *TracedServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm.mux.ServeHTTP(w, r)
}
// Returns a handler that generates a traceresponse header.
// https://github.com/w3c/trace-context/blob/main/spec/21-http_response_header_format.md
func traceResponseHandler(handler http.Handler) http.Handler {
// We use the standard TraceContext propagator, since the formats are identical.
// But the propagator uses "traceparent" header name, so we inject it into a map
// `carrier` and then use the result to set the "tracereponse" header.
var prop propagation.TraceContext
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
carrier := make(map[string]string)
prop.Inject(r.Context(), propagation.MapCarrier(carrier))
w.Header().Add("traceresponse", carrier["traceparent"])
handler.ServeHTTP(w, r)
})
}

@ -0,0 +1,3 @@
Package rpcmetrics implements an OpenTelemetry SpanProcessor that can be used to emit RPC metrics.
This package is copied from jaeger-client-go and adapted to work with OpenTelemtery SDK.

@ -0,0 +1,63 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import "sync"
// normalizedEndpoints is a cache for endpointName -> safeName mappings.
type normalizedEndpoints struct {
names map[string]string
maxSize int
normalizer NameNormalizer
mux sync.RWMutex
}
func newNormalizedEndpoints(maxSize int, normalizer NameNormalizer) *normalizedEndpoints {
return &normalizedEndpoints{
maxSize: maxSize,
normalizer: normalizer,
names: make(map[string]string, maxSize),
}
}
// normalize looks up the name in the cache, if not found it uses normalizer
// to convert the name to a safe name. If called with more than maxSize unique
// names it returns "" for all other names beyond those already cached.
func (n *normalizedEndpoints) normalize(name string) string {
n.mux.RLock()
norm, ok := n.names[name]
l := len(n.names)
n.mux.RUnlock()
if ok {
return norm
}
if l >= n.maxSize {
return ""
}
return n.normalizeWithLock(name)
}
func (n *normalizedEndpoints) normalizeWithLock(name string) string {
norm := n.normalizer.Normalize(name)
n.mux.Lock()
defer n.mux.Unlock()
// cache may have grown while we were not holding the lock
if len(n.names) >= n.maxSize {
return ""
}
n.names[name] = norm
return norm
}

@ -0,0 +1,44 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNormalizedEndpoints(t *testing.T) {
n := newNormalizedEndpoints(1, DefaultNameNormalizer)
assertLen := func(l int) {
n.mux.RLock()
defer n.mux.RUnlock()
assert.Len(t, n.names, l)
}
assert.Equal(t, "ab_cd", n.normalize("ab^cd"), "one translation")
assert.Equal(t, "ab_cd", n.normalize("ab^cd"), "cache hit")
assertLen(1)
assert.Equal(t, "", n.normalize("xys"), "cache overflow")
assertLen(1)
}
func TestNormalizedEndpointsDoubleLocking(t *testing.T) {
n := newNormalizedEndpoints(1, DefaultNameNormalizer)
assert.Equal(t, "ab_cd", n.normalize("ab^cd"), "fill out the cache")
assert.Equal(t, "", n.normalizeWithLock("xys"), "cache overflow")
}

@ -0,0 +1,126 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"sync"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
const (
otherEndpointsPlaceholder = "other"
endpointNameMetricTag = "endpoint"
)
// Metrics is a collection of metrics for an endpoint describing
// throughput, success, errors, and performance.
type Metrics struct {
// RequestCountSuccess is a counter of the total number of successes.
RequestCountSuccess metrics.Counter `metric:"requests" tags:"error=false"`
// RequestCountFailures is a counter of the number of times any failure has been observed.
RequestCountFailures metrics.Counter `metric:"requests" tags:"error=true"`
// RequestLatencySuccess is a latency histogram of successful requests.
RequestLatencySuccess metrics.Timer `metric:"request_latency" tags:"error=false"`
// RequestLatencyFailures is a latency histogram of failed requests.
RequestLatencyFailures metrics.Timer `metric:"request_latency" tags:"error=true"`
// HTTPStatusCode2xx is a counter of the total number of requests with HTTP status code 200-299
HTTPStatusCode2xx metrics.Counter `metric:"http_requests" tags:"status_code=2xx"`
// HTTPStatusCode3xx is a counter of the total number of requests with HTTP status code 300-399
HTTPStatusCode3xx metrics.Counter `metric:"http_requests" tags:"status_code=3xx"`
// HTTPStatusCode4xx is a counter of the total number of requests with HTTP status code 400-499
HTTPStatusCode4xx metrics.Counter `metric:"http_requests" tags:"status_code=4xx"`
// HTTPStatusCode5xx is a counter of the total number of requests with HTTP status code 500-599
HTTPStatusCode5xx metrics.Counter `metric:"http_requests" tags:"status_code=5xx"`
}
func (m *Metrics) recordHTTPStatusCode(statusCode int64) {
switch {
case statusCode >= 200 && statusCode < 300:
m.HTTPStatusCode2xx.Inc(1)
case statusCode >= 300 && statusCode < 400:
m.HTTPStatusCode3xx.Inc(1)
case statusCode >= 400 && statusCode < 500:
m.HTTPStatusCode4xx.Inc(1)
case statusCode >= 500 && statusCode < 600:
m.HTTPStatusCode5xx.Inc(1)
}
}
// MetricsByEndpoint is a registry/cache of metrics for each unique endpoint name.
// Only maxNumberOfEndpoints Metrics are stored, all other endpoint names are mapped
// to a generic endpoint name "other".
type MetricsByEndpoint struct {
metricsFactory metrics.Factory
endpoints *normalizedEndpoints
metricsByEndpoint map[string]*Metrics
mux sync.RWMutex
}
func newMetricsByEndpoint(
metricsFactory metrics.Factory,
normalizer NameNormalizer,
maxNumberOfEndpoints int,
) *MetricsByEndpoint {
return &MetricsByEndpoint{
metricsFactory: metricsFactory,
endpoints: newNormalizedEndpoints(maxNumberOfEndpoints, normalizer),
metricsByEndpoint: make(map[string]*Metrics, maxNumberOfEndpoints+1), // +1 for "other"
}
}
func (m *MetricsByEndpoint) get(endpoint string) *Metrics {
safeName := m.endpoints.normalize(endpoint)
if safeName == "" {
safeName = otherEndpointsPlaceholder
}
m.mux.RLock()
met := m.metricsByEndpoint[safeName]
m.mux.RUnlock()
if met != nil {
return met
}
return m.getWithWriteLock(safeName)
}
// split to make easier to test
func (m *MetricsByEndpoint) getWithWriteLock(safeName string) *Metrics {
m.mux.Lock()
defer m.mux.Unlock()
// it is possible that the name has been already registered after we released
// the read lock and before we grabbed the write lock, so check for that.
if met, ok := m.metricsByEndpoint[safeName]; ok {
return met
}
// it would be nice to create the struct before locking, since Init() is somewhat
// expensive, however some metrics backends (e.g. expvar) may not like duplicate metrics.
met := &Metrics{}
tags := map[string]string{endpointNameMetricTag: safeName}
metrics.Init(met, m.metricsFactory, tags)
m.metricsByEndpoint[safeName] = met
return met
}

@ -0,0 +1,62 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/jaegertracing/jaeger/internal/metricstest"
)
// E.g. tags("key", "value", "key", "value")
func tags(kv ...string) map[string]string {
m := make(map[string]string)
for i := 0; i < len(kv)-1; i += 2 {
m[kv[i]] = kv[i+1]
}
return m
}
func endpointTags(endpoint string, kv ...string) map[string]string {
return tags(append([]string{"endpoint", endpoint}, kv...)...)
}
func TestMetricsByEndpoint(t *testing.T) {
met := metricstest.NewFactory(0)
mbe := newMetricsByEndpoint(met, DefaultNameNormalizer, 2)
m1 := mbe.get("abc1")
m2 := mbe.get("abc1") // from cache
m2a := mbe.getWithWriteLock("abc1") // from cache in double-checked lock
assert.Equal(t, m1, m2)
assert.Equal(t, m1, m2a)
m3 := mbe.get("abc3")
m4 := mbe.get("overflow")
m5 := mbe.get("overflow2")
for _, m := range []*Metrics{m1, m2, m2a, m3, m4, m5} {
m.RequestCountSuccess.Inc(1)
}
met.AssertCounterMetrics(t,
metricstest.ExpectedMetric{Name: "requests", Tags: endpointTags("abc1", "error", "false"), Value: 3},
metricstest.ExpectedMetric{Name: "requests", Tags: endpointTags("abc3", "error", "false"), Value: 1},
metricstest.ExpectedMetric{Name: "requests", Tags: endpointTags("other", "error", "false"), Value: 2},
)
}

@ -0,0 +1,101 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
// NameNormalizer is used to convert the endpoint names to strings
// that can be safely used as tags in the metrics.
type NameNormalizer interface {
Normalize(name string) string
}
// DefaultNameNormalizer converts endpoint names so that they contain only characters
// from the safe charset [a-zA-Z0-9./_]. All other characters are replaced with '_'.
var DefaultNameNormalizer = &SimpleNameNormalizer{
SafeSets: []SafeCharacterSet{
&Range{From: 'a', To: 'z'},
&Range{From: 'A', To: 'Z'},
&Range{From: '0', To: '9'},
&Char{'_'},
&Char{'/'},
&Char{'.'},
},
Replacement: '_',
}
// SimpleNameNormalizer uses a set of safe character sets.
type SimpleNameNormalizer struct {
SafeSets []SafeCharacterSet
Replacement byte
}
// SafeCharacterSet determines if the given character is "safe"
type SafeCharacterSet interface {
IsSafe(c byte) bool
}
// Range implements SafeCharacterSet
type Range struct {
From, To byte
}
// IsSafe implements SafeCharacterSet
func (r *Range) IsSafe(c byte) bool {
return c >= r.From && c <= r.To
}
// Char implements SafeCharacterSet
type Char struct {
Val byte
}
// IsSafe implements SafeCharacterSet
func (ch *Char) IsSafe(c byte) bool {
return c == ch.Val
}
// Normalize checks each character in the string against SafeSets,
// and if it's not safe substitutes it with Replacement.
func (n *SimpleNameNormalizer) Normalize(name string) string {
var retMe []byte
nameBytes := []byte(name)
for i, b := range nameBytes {
if n.safeByte(b) {
if retMe != nil {
retMe[i] = b
}
} else {
if retMe == nil {
retMe = make([]byte, len(nameBytes))
copy(retMe[0:i], nameBytes[0:i])
}
retMe[i] = n.Replacement
}
}
if retMe == nil {
return name
}
return string(retMe)
}
// safeByte checks if b against all safe charsets.
func (n *SimpleNameNormalizer) safeByte(b byte) bool {
for i := range n.SafeSets {
if n.SafeSets[i].IsSafe(b) {
return true
}
}
return false
}

@ -0,0 +1,35 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSimpleNameNormalizer(t *testing.T) {
n := &SimpleNameNormalizer{
SafeSets: []SafeCharacterSet{
&Range{From: 'a', To: 'z'},
&Char{'-'},
},
Replacement: '-',
}
assert.Equal(t, "ab-cd", n.Normalize("ab-cd"), "all valid")
assert.Equal(t, "ab-cd", n.Normalize("ab.cd"), "single mismatch")
assert.Equal(t, "a--cd", n.Normalize("aB-cd"), "range letter mismatch")
}

@ -0,0 +1,92 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"context"
"strconv"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
const defaultMaxNumberOfEndpoints = 200
var _ sdktrace.SpanProcessor = (*Observer)(nil)
// Observer is an observer that can emit RPC metrics.
type Observer struct {
metricsByEndpoint *MetricsByEndpoint
}
// NewObserver creates a new observer that can emit RPC metrics.
func NewObserver(metricsFactory metrics.Factory, normalizer NameNormalizer) *Observer {
return &Observer{
metricsByEndpoint: newMetricsByEndpoint(
metricsFactory,
normalizer,
defaultMaxNumberOfEndpoints,
),
}
}
func (o *Observer) OnStart(parent context.Context, s sdktrace.ReadWriteSpan) {}
func (o *Observer) OnEnd(sp sdktrace.ReadOnlySpan) {
operationName := sp.Name()
if operationName == "" {
return
}
if sp.SpanKind() != trace.SpanKindServer {
return
}
mets := o.metricsByEndpoint.get(operationName)
latency := sp.EndTime().Sub(sp.StartTime())
if status := sp.Status(); status.Code == codes.Error {
mets.RequestCountFailures.Inc(1)
mets.RequestLatencyFailures.Record(latency)
} else {
mets.RequestCountSuccess.Inc(1)
mets.RequestLatencySuccess.Record(latency)
}
for _, attr := range sp.Attributes() {
if string(attr.Key) == string(semconv.HTTPResponseStatusCodeKey) {
if attr.Value.Type() == attribute.INT64 {
mets.recordHTTPStatusCode(attr.Value.AsInt64())
} else if attr.Value.Type() == attribute.STRING {
s := attr.Value.AsString()
if n, err := strconv.Atoi(s); err == nil {
mets.recordHTTPStatusCode(int64(n))
}
}
}
}
}
func (o *Observer) Shutdown(ctx context.Context) error {
return nil
}
func (o *Observer) ForceFlush(ctx context.Context) error {
return nil
}

@ -0,0 +1,149 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
u "github.com/jaegertracing/jaeger/internal/metricstest"
)
type testTracer struct {
metrics *u.Factory
tracer trace.Tracer
}
func withTestTracer(runTest func(tt *testTracer)) {
metrics := u.NewFactory(time.Minute)
defer metrics.Stop()
observer := NewObserver(metrics, DefaultNameNormalizer)
tp := sdktrace.NewTracerProvider(
sdktrace.WithSpanProcessor(observer),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("test"),
)),
)
runTest(&testTracer{
metrics: metrics,
tracer: tp.Tracer("test"),
})
}
func TestObserver(t *testing.T) {
withTestTracer(func(testTracer *testTracer) {
ts := time.Now()
finishOptions := trace.WithTimestamp(ts.Add((50 * time.Millisecond)))
testCases := []struct {
name string
spanKind trace.SpanKind
opNameOverride string
err bool
}{
{name: "local-span", spanKind: trace.SpanKindInternal},
{name: "get-user", spanKind: trace.SpanKindServer},
{name: "get-user", spanKind: trace.SpanKindServer, opNameOverride: "get-user-override"},
{name: "get-user", spanKind: trace.SpanKindServer, err: true},
{name: "get-user-client", spanKind: trace.SpanKindClient},
}
for _, testCase := range testCases {
_, span := testTracer.tracer.Start(
context.Background(),
testCase.name, trace.WithSpanKind(testCase.spanKind),
trace.WithTimestamp(ts),
)
if testCase.opNameOverride != "" {
span.SetName(testCase.opNameOverride)
}
if testCase.err {
span.SetStatus(codes.Error, "An error occurred")
}
span.End(finishOptions)
}
testTracer.metrics.AssertCounterMetrics(t,
u.ExpectedMetric{Name: "requests", Tags: endpointTags("local_span", "error", "false"), Value: 0},
u.ExpectedMetric{Name: "requests", Tags: endpointTags("get_user", "error", "false"), Value: 1},
u.ExpectedMetric{Name: "requests", Tags: endpointTags("get_user", "error", "true"), Value: 1},
u.ExpectedMetric{Name: "requests", Tags: endpointTags("get_user_override", "error", "false"), Value: 1},
u.ExpectedMetric{Name: "requests", Tags: endpointTags("get_user_client", "error", "false"), Value: 0},
)
// TODO something wrong with string generation, .P99 should not be appended to the tag
// as a result we cannot use u.AssertGaugeMetrics
_, g := testTracer.metrics.Snapshot()
assert.EqualValues(t, 51, g["request_latency|endpoint=get_user|error=false.P99"])
assert.EqualValues(t, 51, g["request_latency|endpoint=get_user|error=true.P99"])
})
}
func TestTags(t *testing.T) {
type tagTestCase struct {
attr attribute.KeyValue
err bool
metrics []u.ExpectedMetric
}
testCases := []tagTestCase{
{err: false, metrics: []u.ExpectedMetric{
{Name: "requests", Value: 1, Tags: tags("error", "false")},
}},
{err: true, metrics: []u.ExpectedMetric{
{Name: "requests", Value: 1, Tags: tags("error", "true")},
}},
}
for i := 200; i <= 500; i += 100 {
testCases = append(testCases, tagTestCase{
attr: semconv.HTTPResponseStatusCode(i),
metrics: []u.ExpectedMetric{
{Name: "http_requests", Value: 1, Tags: tags("status_code", fmt.Sprintf("%dxx", i/100))},
},
})
}
for _, testCase := range testCases {
for i := range testCase.metrics {
testCase.metrics[i].Tags["endpoint"] = "span"
}
t.Run(fmt.Sprintf("%s-%v", testCase.attr.Key, testCase.attr.Value), func(t *testing.T) {
withTestTracer(func(testTracer *testTracer) {
_, span := testTracer.tracer.Start(
context.Background(),
"span", trace.WithSpanKind(trace.SpanKindServer),
)
span.SetAttributes(testCase.attr)
if testCase.err {
span.SetStatus(codes.Error, "An error occurred")
}
span.End()
testTracer.metrics.AssertCounterMetrics(t, testCase.metrics...)
})
})
}
}

@ -0,0 +1,25 @@
// Copyright (c) 2023 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpcmetrics
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,63 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"time"
)
var (
// 'frontend' service
// RouteWorkerPoolSize is the size of the worker pool used to query `route` service.
// Can be overwritten from command line.
RouteWorkerPoolSize = 3
// 'customer' service
// MySQLGetDelay is how long retrieving a customer record takes.
// Using large value mostly because I cannot click the button fast enough to cause a queue.
MySQLGetDelay = 300 * time.Millisecond
// MySQLGetDelayStdDev is standard deviation
MySQLGetDelayStdDev = MySQLGetDelay / 10
// MySQLMutexDisabled controls whether there is a mutex guarding db query execution.
// When not disabled it simulates a misconfigured connection pool of size 1.
MySQLMutexDisabled = false
// 'driver' service
// RedisFindDelay is how long finding closest drivers takes.
RedisFindDelay = 20 * time.Millisecond
// RedisFindDelayStdDev is standard deviation.
RedisFindDelayStdDev = RedisFindDelay / 4
// RedisGetDelay is how long retrieving a driver record takes.
RedisGetDelay = 10 * time.Millisecond
// RedisGetDelayStdDev is standard deviation
RedisGetDelayStdDev = RedisGetDelay / 4
// 'route' service
// RouteCalcDelay is how long a route calculation takes
RouteCalcDelay = 50 * time.Millisecond
// RouteCalcDelayStdDev is standard deviation
RouteCalcDelayStdDev = RouteCalcDelay / 4
)

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,55 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package customer
import (
"context"
"fmt"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
)
// Client is a remote client that implements customer.Interface
type Client struct {
logger log.Factory
client *tracing.HTTPClient
hostPort string
}
// NewClient creates a new customer.Client
func NewClient(tracer trace.TracerProvider, logger log.Factory, hostPort string) *Client {
return &Client{
logger: logger,
client: tracing.NewHTTPClient(tracer),
hostPort: hostPort,
}
}
// Get implements customer.Interface#Get as an RPC
func (c *Client) Get(ctx context.Context, customerID int) (*Customer, error) {
c.logger.For(ctx).Info("Getting customer", zap.Int("customer_id", customerID))
url := fmt.Sprintf("http://"+c.hostPort+"/customer?customer=%d", customerID)
var customer Customer
if err := c.client.GetJSON(ctx, "/customer", url, &customer); err != nil {
return nil, err
}
return &customer, nil
}

@ -0,0 +1,100 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package customer
import (
"context"
"errors"
"fmt"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/delay"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/examples/hotrod/services/config"
)
// database simulates Customer repository implemented on top of an SQL database
type database struct {
tracer trace.Tracer
logger log.Factory
customers map[int]*Customer
lock *tracing.Mutex
}
func newDatabase(tracer trace.Tracer, logger log.Factory) *database {
return &database{
tracer: tracer,
logger: logger,
lock: &tracing.Mutex{
SessionBaggageKey: "request",
LogFactory: logger,
},
customers: map[int]*Customer{
123: {
ID: "123",
Name: "Rachel's_Floral_Designs",
Location: "115,277",
},
567: {
ID: "567",
Name: "Amazing_Coffee_Roasters",
Location: "211,653",
},
392: {
ID: "392",
Name: "Trom_Chocolatier",
Location: "577,322",
},
731: {
ID: "731",
Name: "Japanese_Desserts",
Location: "728,326",
},
},
}
}
func (d *database) Get(ctx context.Context, customerID int) (*Customer, error) {
d.logger.For(ctx).Info("Loading customer", zap.Int("customer_id", customerID))
ctx, span := d.tracer.Start(ctx, "SQL SELECT", trace.WithSpanKind(trace.SpanKindClient))
span.SetAttributes(
semconv.PeerServiceKey.String("mysql"),
attribute.
Key("sql.query").
String(fmt.Sprintf("SELECT * FROM customer WHERE customer_id=%d", customerID)),
)
defer span.End()
if !config.MySQLMutexDisabled {
// simulate misconfigured connection pool that only gives one connection at a time
d.lock.Lock(ctx)
defer d.lock.Unlock()
}
// simulate RPC delay
delay.Sleep(config.MySQLGetDelay, config.MySQLGetDelayStdDev)
if customer, ok := d.customers[customerID]; ok {
return customer, nil
}
return nil, errors.New("invalid customer ID")
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package customer
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,32 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package customer
import (
"context"
)
// Customer contains data about a customer.
type Customer struct {
ID string
Name string
Location string
}
// Interface exposed by the Customer service.
type Interface interface {
Get(ctx context.Context, customerID int) (*Customer, error)
}

@ -0,0 +1,105 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package customer
import (
"encoding/json"
"net/http"
"strconv"
"time"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/httperr"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
// Server implements Customer service
type Server struct {
hostPort string
tracer trace.TracerProvider
logger log.Factory
database *database
}
// NewServer creates a new customer.Server
func NewServer(hostPort string, otelExporter string, metricsFactory metrics.Factory, logger log.Factory) *Server {
return &Server{
hostPort: hostPort,
tracer: tracing.InitOTEL("customer", otelExporter, metricsFactory, logger),
logger: logger,
database: newDatabase(
tracing.InitOTEL("mysql", otelExporter, metricsFactory, logger).Tracer("mysql"),
logger.With(zap.String("component", "mysql")),
),
}
}
// Run starts the Customer server
func (s *Server) Run() error {
mux := s.createServeMux()
s.logger.Bg().Info("Starting", zap.String("address", "http://"+s.hostPort))
server := &http.Server{
Addr: s.hostPort,
Handler: mux,
ReadHeaderTimeout: 3 * time.Second,
}
return server.ListenAndServe()
}
func (s *Server) createServeMux() http.Handler {
mux := tracing.NewServeMux(false, s.tracer, s.logger)
mux.Handle("/customer", http.HandlerFunc(s.customer))
return mux
}
func (s *Server) customer(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s.logger.For(ctx).Info("HTTP request received", zap.String("method", r.Method), zap.Stringer("url", r.URL))
if err := r.ParseForm(); httperr.HandleError(w, err, http.StatusBadRequest) {
s.logger.For(ctx).Error("bad request", zap.Error(err))
return
}
customer := r.Form.Get("customer")
if customer == "" {
http.Error(w, "Missing required 'customer' parameter", http.StatusBadRequest)
return
}
customerID, err := strconv.Atoi(customer)
if err != nil {
http.Error(w, "Parameter 'customer' is not an integer", http.StatusBadRequest)
return
}
response, err := s.database.Get(ctx, customerID)
if httperr.HandleError(w, err, http.StatusInternalServerError) {
s.logger.For(ctx).Error("request failed", zap.Error(err))
return
}
data, err := json.Marshal(response)
if httperr.HandleError(w, err, http.StatusInternalServerError) {
s.logger.For(ctx).Error("cannot marshal response", zap.Error(err))
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}

@ -0,0 +1,75 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"context"
"time"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
)
// Client is a remote client that implements driver.Interface
type Client struct {
logger log.Factory
client DriverServiceClient
}
// NewClient creates a new driver.Client
func NewClient(tracerProvider trace.TracerProvider, logger log.Factory, hostPort string) *Client {
conn, err := grpc.Dial(hostPort,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(tracerProvider))),
)
if err != nil {
logger.Bg().Fatal("Cannot create gRPC connection", zap.Error(err))
}
client := NewDriverServiceClient(conn)
return &Client{
logger: logger,
client: client,
}
}
// FindNearest implements driver.Interface#FindNearest as an RPC
func (c *Client) FindNearest(ctx context.Context, location string) ([]Driver, error) {
c.logger.For(ctx).Info("Finding nearest drivers", zap.String("location", location))
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
response, err := c.client.FindNearest(ctx, &DriverLocationRequest{Location: location})
if err != nil {
return nil, err
}
return fromProto(response), nil
}
func fromProto(response *DriverLocationResponse) []Driver {
retMe := make([]Driver, len(response.Locations))
for i, result := range response.Locations {
retMe[i] = Driver{
DriverID: result.DriverID,
Location: result.Location,
}
}
return retMe
}

@ -0,0 +1,254 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: examples/hotrod/services/driver/driver.proto
package driver
import (
context "context"
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type DriverLocationRequest struct {
Location string `protobuf:"bytes,1,opt,name=location,proto3" json:"location,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DriverLocationRequest) Reset() { *m = DriverLocationRequest{} }
func (m *DriverLocationRequest) String() string { return proto.CompactTextString(m) }
func (*DriverLocationRequest) ProtoMessage() {}
func (*DriverLocationRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cdcd28b7ebdcd54f, []int{0}
}
func (m *DriverLocationRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DriverLocationRequest.Unmarshal(m, b)
}
func (m *DriverLocationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DriverLocationRequest.Marshal(b, m, deterministic)
}
func (m *DriverLocationRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_DriverLocationRequest.Merge(m, src)
}
func (m *DriverLocationRequest) XXX_Size() int {
return xxx_messageInfo_DriverLocationRequest.Size(m)
}
func (m *DriverLocationRequest) XXX_DiscardUnknown() {
xxx_messageInfo_DriverLocationRequest.DiscardUnknown(m)
}
var xxx_messageInfo_DriverLocationRequest proto.InternalMessageInfo
func (m *DriverLocationRequest) GetLocation() string {
if m != nil {
return m.Location
}
return ""
}
type DriverLocation struct {
DriverID string `protobuf:"bytes,1,opt,name=driverID,proto3" json:"driverID,omitempty"`
Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DriverLocation) Reset() { *m = DriverLocation{} }
func (m *DriverLocation) String() string { return proto.CompactTextString(m) }
func (*DriverLocation) ProtoMessage() {}
func (*DriverLocation) Descriptor() ([]byte, []int) {
return fileDescriptor_cdcd28b7ebdcd54f, []int{1}
}
func (m *DriverLocation) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DriverLocation.Unmarshal(m, b)
}
func (m *DriverLocation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DriverLocation.Marshal(b, m, deterministic)
}
func (m *DriverLocation) XXX_Merge(src proto.Message) {
xxx_messageInfo_DriverLocation.Merge(m, src)
}
func (m *DriverLocation) XXX_Size() int {
return xxx_messageInfo_DriverLocation.Size(m)
}
func (m *DriverLocation) XXX_DiscardUnknown() {
xxx_messageInfo_DriverLocation.DiscardUnknown(m)
}
var xxx_messageInfo_DriverLocation proto.InternalMessageInfo
func (m *DriverLocation) GetDriverID() string {
if m != nil {
return m.DriverID
}
return ""
}
func (m *DriverLocation) GetLocation() string {
if m != nil {
return m.Location
}
return ""
}
type DriverLocationResponse struct {
Locations []*DriverLocation `protobuf:"bytes,1,rep,name=locations,proto3" json:"locations,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DriverLocationResponse) Reset() { *m = DriverLocationResponse{} }
func (m *DriverLocationResponse) String() string { return proto.CompactTextString(m) }
func (*DriverLocationResponse) ProtoMessage() {}
func (*DriverLocationResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_cdcd28b7ebdcd54f, []int{2}
}
func (m *DriverLocationResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DriverLocationResponse.Unmarshal(m, b)
}
func (m *DriverLocationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_DriverLocationResponse.Marshal(b, m, deterministic)
}
func (m *DriverLocationResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_DriverLocationResponse.Merge(m, src)
}
func (m *DriverLocationResponse) XXX_Size() int {
return xxx_messageInfo_DriverLocationResponse.Size(m)
}
func (m *DriverLocationResponse) XXX_DiscardUnknown() {
xxx_messageInfo_DriverLocationResponse.DiscardUnknown(m)
}
var xxx_messageInfo_DriverLocationResponse proto.InternalMessageInfo
func (m *DriverLocationResponse) GetLocations() []*DriverLocation {
if m != nil {
return m.Locations
}
return nil
}
func init() {
proto.RegisterType((*DriverLocationRequest)(nil), "driver.DriverLocationRequest")
proto.RegisterType((*DriverLocation)(nil), "driver.DriverLocation")
proto.RegisterType((*DriverLocationResponse)(nil), "driver.DriverLocationResponse")
}
func init() {
proto.RegisterFile("examples/hotrod/services/driver/driver.proto", fileDescriptor_cdcd28b7ebdcd54f)
}
var fileDescriptor_cdcd28b7ebdcd54f = []byte{
// 207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x49, 0xad, 0x48, 0xcc,
0x2d, 0xc8, 0x49, 0x2d, 0xd6, 0xcf, 0xc8, 0x2f, 0x29, 0xca, 0x4f, 0xd1, 0x2f, 0x4e, 0x2d, 0x2a,
0xcb, 0x4c, 0x4e, 0x2d, 0xd6, 0x4f, 0x29, 0xca, 0x2c, 0x4b, 0x2d, 0x82, 0x52, 0x7a, 0x05, 0x45,
0xf9, 0x25, 0xf9, 0x42, 0x6c, 0x10, 0x9e, 0x92, 0x31, 0x97, 0xa8, 0x0b, 0x98, 0xe5, 0x93, 0x9f,
0x9c, 0x58, 0x92, 0x99, 0x9f, 0x17, 0x94, 0x5a, 0x58, 0x9a, 0x5a, 0x5c, 0x22, 0x24, 0xc5, 0xc5,
0x91, 0x03, 0x15, 0x92, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0xf3, 0x95, 0x3c, 0xb8, 0xf8,
0x50, 0x35, 0x81, 0x54, 0x43, 0x0c, 0xf4, 0x74, 0x81, 0xa9, 0x86, 0xf1, 0x51, 0x4c, 0x62, 0x42,
0x33, 0xc9, 0x8f, 0x4b, 0x0c, 0xdd, 0xfa, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x13, 0x2e,
0x4e, 0x98, 0xaa, 0x62, 0x09, 0x46, 0x05, 0x66, 0x0d, 0x6e, 0x23, 0x31, 0x3d, 0xa8, 0x17, 0xd0,
0xb4, 0x20, 0x14, 0x1a, 0xc5, 0x72, 0xf1, 0x42, 0x24, 0x83, 0x21, 0x9e, 0x17, 0xf2, 0xe1, 0xe2,
0x76, 0xcb, 0xcc, 0x4b, 0xf1, 0x4b, 0x4d, 0x2c, 0x02, 0xf9, 0x4a, 0x16, 0x87, 0x11, 0x10, 0x4f,
0x4b, 0xc9, 0xe1, 0x92, 0x86, 0x38, 0xca, 0x89, 0x23, 0x0a, 0x1a, 0x6e, 0x49, 0x6c, 0xe0, 0x60,
0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x90, 0x0b, 0x66, 0x76, 0x01, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// DriverServiceClient is the client API for DriverService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type DriverServiceClient interface {
FindNearest(ctx context.Context, in *DriverLocationRequest, opts ...grpc.CallOption) (*DriverLocationResponse, error)
}
type driverServiceClient struct {
cc *grpc.ClientConn
}
func NewDriverServiceClient(cc *grpc.ClientConn) DriverServiceClient {
return &driverServiceClient{cc}
}
func (c *driverServiceClient) FindNearest(ctx context.Context, in *DriverLocationRequest, opts ...grpc.CallOption) (*DriverLocationResponse, error) {
out := new(DriverLocationResponse)
err := c.cc.Invoke(ctx, "/driver.DriverService/FindNearest", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DriverServiceServer is the server API for DriverService service.
type DriverServiceServer interface {
FindNearest(context.Context, *DriverLocationRequest) (*DriverLocationResponse, error)
}
// UnimplementedDriverServiceServer can be embedded to have forward compatible implementations.
type UnimplementedDriverServiceServer struct {
}
func (*UnimplementedDriverServiceServer) FindNearest(ctx context.Context, req *DriverLocationRequest) (*DriverLocationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FindNearest not implemented")
}
func RegisterDriverServiceServer(s *grpc.Server, srv DriverServiceServer) {
s.RegisterService(&_DriverService_serviceDesc, srv)
}
func _DriverService_FindNearest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DriverLocationRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DriverServiceServer).FindNearest(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/driver.DriverService/FindNearest",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DriverServiceServer).FindNearest(ctx, req.(*DriverLocationRequest))
}
return interceptor(ctx, in, info, handler)
}
var _DriverService_serviceDesc = grpc.ServiceDesc{
ServiceName: "driver.DriverService",
HandlerType: (*DriverServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "FindNearest",
Handler: _DriverService_FindNearest_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "examples/hotrod/services/driver/driver.proto",
}

@ -0,0 +1,35 @@
// Copyright (c) 2020 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax="proto3";
package driver;
option go_package = "driver";
message DriverLocationRequest {
string location = 1;
}
message DriverLocation {
string driverID = 1;
string location = 2;
}
message DriverLocationResponse {
repeated DriverLocation locations = 1;
}
service DriverService {
rpc FindNearest(DriverLocationRequest) returns (DriverLocationResponse);
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,31 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"context"
)
// Driver describes a driver and the current car location.
type Driver struct {
DriverID string
Location string
}
// Interface exposed by the Driver service.
type Interface interface {
FindNearest(ctx context.Context, location string) ([]Driver, error)
}

@ -0,0 +1,112 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"context"
"errors"
"fmt"
"math/rand"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/delay"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/examples/hotrod/services/config"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
// Redis is a simulator of remote Redis cache
type Redis struct {
tracer trace.Tracer // simulate redis as a separate process
logger log.Factory
errorSimulator
}
func newRedis(otelExporter string, metricsFactory metrics.Factory, logger log.Factory) *Redis {
tp := tracing.InitOTEL("redis-manual", otelExporter, metricsFactory, logger)
return &Redis{
tracer: tp.Tracer("redis-manual"),
logger: logger,
}
}
// FindDriverIDs finds IDs of drivers who are near the location.
func (r *Redis) FindDriverIDs(ctx context.Context, location string) []string {
ctx, span := r.tracer.Start(ctx, "FindDriverIDs", trace.WithSpanKind(trace.SpanKindClient))
span.SetAttributes(attribute.Key("param.driver.location").String(location))
defer span.End()
// simulate RPC delay
delay.Sleep(config.RedisFindDelay, config.RedisFindDelayStdDev)
drivers := make([]string, 10)
for i := range drivers {
// #nosec
drivers[i] = fmt.Sprintf("T7%05dC", rand.Int()%100000)
}
r.logger.For(ctx).Info("Found drivers", zap.Strings("drivers", drivers))
return drivers
}
// GetDriver returns driver and the current car location
func (r *Redis) GetDriver(ctx context.Context, driverID string) (Driver, error) {
ctx, span := r.tracer.Start(ctx, "GetDriver", trace.WithSpanKind(trace.SpanKindClient))
span.SetAttributes(attribute.Key("param.driverID").String(driverID))
defer span.End()
// simulate RPC delay
delay.Sleep(config.RedisGetDelay, config.RedisGetDelayStdDev)
if err := r.checkError(); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "An error occurred")
r.logger.For(ctx).Error("redis timeout", zap.String("driver_id", driverID), zap.Error(err))
return Driver{}, err
}
r.logger.For(ctx).Info("Got driver's ID", zap.String("driverID", driverID))
// #nosec
return Driver{
DriverID: driverID,
Location: fmt.Sprintf("%d,%d", rand.Int()%1000, rand.Int()%1000),
}, nil
}
var errTimeout = errors.New("redis timeout")
type errorSimulator struct {
sync.Mutex
countTillError int
}
func (es *errorSimulator) checkError() error {
es.Lock()
es.countTillError--
if es.countTillError > 0 {
es.Unlock()
return nil
}
es.countTillError = 5
es.Unlock()
delay.Sleep(2*config.RedisGetDelay, 0) // add more delay for "timeout"
return errTimeout
}

@ -0,0 +1,110 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"context"
"encoding/json"
"net"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.uber.org/zap"
"google.golang.org/grpc"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/pkg/metrics"
)
// Server implements jaeger-demo-frontend service
type Server struct {
hostPort string
logger log.Factory
redis *Redis
server *grpc.Server
}
var _ DriverServiceServer = (*Server)(nil)
// NewServer creates a new driver.Server
func NewServer(hostPort string, otelExporter string, metricsFactory metrics.Factory, logger log.Factory) *Server {
tracerProvider := tracing.InitOTEL("driver", otelExporter, metricsFactory, logger)
server := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler(otelgrpc.WithTracerProvider(tracerProvider))),
)
return &Server{
hostPort: hostPort,
logger: logger,
server: server,
redis: newRedis(otelExporter, metricsFactory, logger),
}
}
// Run starts the Driver server
func (s *Server) Run() error {
lis, err := net.Listen("tcp", s.hostPort)
if err != nil {
s.logger.Bg().Fatal("Unable to create http listener", zap.Error(err))
}
RegisterDriverServiceServer(s.server, s)
s.logger.Bg().Info("Starting", zap.String("address", s.hostPort), zap.String("type", "gRPC"))
err = s.server.Serve(lis)
if err != nil {
s.logger.Bg().Fatal("Unable to start gRPC server", zap.Error(err))
}
return err
}
// FindNearest implements gRPC driver interface
func (s *Server) FindNearest(ctx context.Context, location *DriverLocationRequest) (*DriverLocationResponse, error) {
s.logger.For(ctx).Info("Searching for nearby drivers", zap.String("location", location.Location))
driverIDs := s.redis.FindDriverIDs(ctx, location.Location)
locations := make([]*DriverLocation, len(driverIDs))
for i, driverID := range driverIDs {
var drv Driver
var err error
for i := 0; i < 3; i++ {
drv, err = s.redis.GetDriver(ctx, driverID)
if err == nil {
break
}
s.logger.For(ctx).Error("Retrying GetDriver after error", zap.Int("retry_no", i+1), zap.Error(err))
}
if err != nil {
s.logger.For(ctx).Error("Failed to get driver after 3 attempts", zap.Error(err))
return nil, err
}
locations[i] = &DriverLocation{
DriverID: drv.DriverID,
Location: drv.Location,
}
}
s.logger.For(ctx).Info(
"Search successful",
zap.Int("driver_count", len(locations)),
zap.String("locations", toJSON(locations)),
)
return &DriverLocationResponse{Locations: locations}, nil
}
func toJSON(v any) string {
str, err := json.Marshal(v)
if err != nil {
return err.Error()
}
return string(str)
}

@ -0,0 +1,147 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package frontend
import (
"context"
"errors"
"math"
"sync"
"time"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/pool"
"github.com/jaegertracing/jaeger/examples/hotrod/services/config"
"github.com/jaegertracing/jaeger/examples/hotrod/services/customer"
"github.com/jaegertracing/jaeger/examples/hotrod/services/driver"
"github.com/jaegertracing/jaeger/examples/hotrod/services/route"
)
type bestETA struct {
customer customer.Interface
driver driver.Interface
route route.Interface
pool *pool.Pool
logger log.Factory
}
// Response contains ETA for a trip.
type Response struct {
Driver string
ETA time.Duration
}
func newBestETA(tracer trace.TracerProvider, logger log.Factory, options ConfigOptions) *bestETA {
return &bestETA{
customer: customer.NewClient(
tracer,
logger.With(zap.String("component", "customer_client")),
options.CustomerHostPort,
),
driver: driver.NewClient(
tracer,
logger.With(zap.String("component", "driver_client")),
options.DriverHostPort,
),
route: route.NewClient(
tracer,
logger.With(zap.String("component", "route_client")),
options.RouteHostPort,
),
pool: pool.New(config.RouteWorkerPoolSize),
logger: logger,
}
}
func (eta *bestETA) Get(ctx context.Context, customerID int) (*Response, error) {
customer, err := eta.customer.Get(ctx, customerID)
if err != nil {
return nil, err
}
eta.logger.For(ctx).Info("Found customer", zap.Any("customer", customer))
m, err := baggage.NewMember("customer", customer.Name)
if err != nil {
eta.logger.For(ctx).Error("cannot create baggage member", zap.Error(err))
}
bag := baggage.FromContext(ctx)
bag, err = bag.SetMember(m)
if err != nil {
eta.logger.For(ctx).Error("cannot set baggage member", zap.Error(err))
}
ctx = baggage.ContextWithBaggage(ctx, bag)
drivers, err := eta.driver.FindNearest(ctx, customer.Location)
if err != nil {
return nil, err
}
eta.logger.For(ctx).Info("Found drivers", zap.Any("drivers", drivers))
results := eta.getRoutes(ctx, customer, drivers)
eta.logger.For(ctx).Info("Found routes", zap.Any("routes", results))
resp := &Response{ETA: math.MaxInt64}
for _, result := range results {
if result.err != nil {
return nil, err
}
if result.route.ETA < resp.ETA {
resp.ETA = result.route.ETA
resp.Driver = result.driver
}
}
if resp.Driver == "" {
return nil, errors.New("no routes found")
}
eta.logger.For(ctx).Info("Dispatch successful", zap.String("driver", resp.Driver), zap.String("eta", resp.ETA.String()))
return resp, nil
}
type routeResult struct {
driver string
route *route.Route
err error
}
// getRoutes calls Route service for each (customer, driver) pair
func (eta *bestETA) getRoutes(ctx context.Context, customer *customer.Customer, drivers []driver.Driver) []routeResult {
results := make([]routeResult, 0, len(drivers))
wg := sync.WaitGroup{}
routesLock := sync.Mutex{}
for _, dd := range drivers {
wg.Add(1)
driver := dd // capture loop var
// Use worker pool to (potentially) execute requests in parallel
eta.pool.Execute(func() {
route, err := eta.route.FindRoute(ctx, driver.Location, customer.Location)
routesLock.Lock()
results = append(results, routeResult{
driver: driver.DriverID,
route: route,
err: err,
})
routesLock.Unlock()
wg.Done()
})
}
wg.Wait()
return results
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package frontend
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,143 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package frontend
import (
"embed"
"encoding/json"
"expvar"
"net/http"
"path"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/httperr"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/pkg/httpfs"
)
//go:embed web_assets/*
var assetFS embed.FS
// Server implements jaeger-demo-frontend service
type Server struct {
hostPort string
tracer trace.TracerProvider
logger log.Factory
bestETA *bestETA
assetFS http.FileSystem
basepath string
jaegerUI string
}
// ConfigOptions used to make sure service clients
// can find correct server ports
type ConfigOptions struct {
FrontendHostPort string
DriverHostPort string
CustomerHostPort string
RouteHostPort string
Basepath string
JaegerUI string
}
// NewServer creates a new frontend.Server
func NewServer(options ConfigOptions, tracer trace.TracerProvider, logger log.Factory) *Server {
return &Server{
hostPort: options.FrontendHostPort,
tracer: tracer,
logger: logger,
bestETA: newBestETA(tracer, logger, options),
assetFS: httpfs.PrefixedFS("web_assets", http.FS(assetFS)),
basepath: options.Basepath,
jaegerUI: options.JaegerUI,
}
}
// Run starts the frontend server
func (s *Server) Run() error {
mux := s.createServeMux()
s.logger.Bg().Info("Starting", zap.String("address", "http://"+path.Join(s.hostPort, s.basepath)))
server := &http.Server{
Addr: s.hostPort,
Handler: mux,
ReadHeaderTimeout: 3 * time.Second,
}
return server.ListenAndServe()
}
func (s *Server) createServeMux() http.Handler {
mux := tracing.NewServeMux(true, s.tracer, s.logger)
p := path.Join("/", s.basepath)
mux.Handle(p, http.StripPrefix(p, http.FileServer(s.assetFS)))
mux.Handle(path.Join(p, "/dispatch"), http.HandlerFunc(s.dispatch))
mux.Handle(path.Join(p, "/config"), http.HandlerFunc(s.config))
mux.Handle(path.Join(p, "/debug/vars"), expvar.Handler()) // expvar
mux.Handle(path.Join(p, "/metrics"), promhttp.Handler()) // Prometheus
return mux
}
func (s *Server) config(w http.ResponseWriter, r *http.Request) {
config := map[string]string{
"jaeger": s.jaegerUI,
}
s.writeResponse(config, w, r)
}
func (s *Server) dispatch(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s.logger.For(ctx).Info("HTTP request received", zap.String("method", r.Method), zap.Stringer("url", r.URL))
if err := r.ParseForm(); httperr.HandleError(w, err, http.StatusBadRequest) {
s.logger.For(ctx).Error("bad request", zap.Error(err))
return
}
customer := r.Form.Get("customer")
if customer == "" {
http.Error(w, "Missing required 'customer' parameter", http.StatusBadRequest)
return
}
customerID, err := strconv.Atoi(customer)
if err != nil {
http.Error(w, "Parameter 'customer' is not an integer", http.StatusBadRequest)
return
}
// TODO distinguish between user errors (such as invalid customer ID) and server failures
response, err := s.bestETA.Get(ctx, customerID)
if httperr.HandleError(w, err, http.StatusInternalServerError) {
s.logger.For(ctx).Error("request failed", zap.Error(err))
return
}
s.writeResponse(response, w, r)
}
func (s *Server) writeResponse(response interface{}, w http.ResponseWriter, r *http.Request) {
data, err := json.Marshal(response)
if httperr.HandleError(w, err, http.StatusInternalServerError) {
s.logger.For(r.Context()).Error("cannot marshal response", zap.Error(err))
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,131 @@
<html>
<meta charset="ISO-8859-1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<head>
<title>HotROD - Rides On Demand</title>
<script src="./code.jquery.com_jquery-3.7.0.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<style>
.uuid { margin-top: 15px; }
.hotrod-button { padding: 20px; cursor: pointer; margin-top: 10px; }
.hotrod-button:hover { cursor: pointer; filter: brightness(85%); }
#hotrod-log { margin-top: 15px; }
#tip { margin-top: 15px; }
</style>
</head>
<body>
<div class="container">
<div class="uuid alert alert-info"></div>
<center>
<h1>Hot R.O.D.</h1>
<h4>🚗 <em>Rides On Demand</em> 🚗</h4>
<div class="row">
<div class="col-md-3 col-sm-6">
<span
class="btn btn-info btn-block hotrod-button"
data-customer="123">Rachel's Floral Designs</span>
</div>
<div class="col-md-3 col-sm-6">
<span
class="btn btn-info btn-block hotrod-button"
data-customer="392">Trom Chocolatier</span>
</div>
<div class="col-md-3 col-sm-6">
<span
class="btn btn-info btn-block hotrod-button"
data-customer="731">Japanese Desserts</span>
</div>
<div class="col-md-3 col-sm-6">
<span
class="btn btn-info btn-block hotrod-button"
data-customer="567">Amazing Coffee Roasters</span>
</div>
</div>
<div id="tip">Click on customer name above to order a car.</div>
<div id="hotrod-log" class="lead"></div>
</center>
</div>
</body>
<script>
function formatDuration(duration) {
const d = duration / (1000000 * 1000 * 60);
const units = 'min';
return Math.round(d) + units;
}
function parseTraceResponse(value) {
const VERSION = '00';
const VERSION_PART = '(?!ff)[\\da-f]{2}';
const TRACE_ID_PART = '(?![0]{32})[\\da-f]{32}';
const PARENT_ID_PART = '(?![0]{16})[\\da-f]{16}';
const FLAGS_PART = '[\\da-f]{2}';
const TRACE_PARENT_REGEX = new RegExp(
`^\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\s?$`
);
const match = TRACE_PARENT_REGEX.exec(value);
return (match) ? match[2] : null;
}
const clientUUID = Math.round(Math.random() * 10000);
var lastRequestID = 0;
$(".uuid").html(`Your web client's id: <strong>${clientUUID}</strong>`);
$(".hotrod-button").click(function(evt) {
lastRequestID++;
const requestID = clientUUID + "-" + lastRequestID;
const freshCar = $($("#hotrod-log").prepend('<div class="fresh-car"><em>Dispatching a car...[req: '+requestID+']</em></div>').children()[0]);
const customer = evt.target.dataset.customer;
const headers = {
'baggage': 'session=' + clientUUID + ', request=' + requestID
};
console.log('sending headers', headers);
// Use current URI as basepath for ajax requests
var pathPrefix = window.location.pathname;
pathPrefix = pathPrefix != "/" ? pathPrefix : '';
// TODO this should be done on page load, not on every button click
var config = {};
$.ajax(pathPrefix + '/config?nonse=' + Math.random(), {
method: 'GET',
success: function(data, textStatus) {
console.log('config', data);
config = data;
},
});
const before = Date.now();
$.ajax(pathPrefix + '/dispatch?customer=' + customer + '&nonse=' + Math.random(), {
headers: headers,
method: 'GET',
success: function(data, textStatus, xhr) {
const after = Date.now();
const traceResponse = xhr.getResponseHeader('traceresponse');
const traceID = parseTraceResponse(traceResponse);
console.log('response', data);
console.log('traceResponse', traceResponse, 'traceID', traceID);
const duration = formatDuration(data.ETA);
var traceLink = '';
if (config && config['jaeger']) {
const jaeger = config['jaeger'];
const findURL = `/search?limit=20&lookback=1h&service=frontend&tags=%7B%22driver%22%3A%22${data.Driver}%22%7D`;
traceLink = ` [<a href="${jaeger}${findURL}" target="_blank">find trace</a>]`;
if (traceID) {
traceLink += ` [<a href="${jaeger}/trace/${traceID}" target="_blank">open trace</a>]`;
}
}
freshCar.html(`HotROD <b>${data.Driver}</b> arriving in ${duration} [req: ${requestID}, latency: ${after-before}ms] ${traceLink}`);
},
});
});
</script>
</html>

@ -0,0 +1,59 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route
import (
"context"
"net/url"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
)
// Client is a remote client that implements route.Interface
type Client struct {
logger log.Factory
client *tracing.HTTPClient
hostPort string
}
// NewClient creates a new route.Client
func NewClient(tracer trace.TracerProvider, logger log.Factory, hostPort string) *Client {
return &Client{
logger: logger,
client: tracing.NewHTTPClient(tracer),
hostPort: hostPort,
}
}
// FindRoute implements route.Interface#FindRoute as an RPC
func (c *Client) FindRoute(ctx context.Context, pickup, dropoff string) (*Route, error) {
c.logger.For(ctx).Info("Finding route", zap.String("pickup", pickup), zap.String("dropoff", dropoff))
v := url.Values{}
v.Set("pickup", pickup)
v.Set("dropoff", dropoff)
url := "http://" + c.hostPort + "/route?" + v.Encode()
var route Route
if err := c.client.GetJSON(ctx, "/route", url, &route); err != nil {
c.logger.For(ctx).Error("Error getting route", zap.Error(err))
return nil, err
}
return &route, nil
}

@ -0,0 +1,25 @@
// Copyright (c) 2024 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route
import (
"testing"
"github.com/jaegertracing/jaeger/pkg/testutils"
)
func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

@ -0,0 +1,33 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route
import (
"context"
"time"
)
// Route describes a route between Pickup and Dropoff locations and expected time to arrival.
type Route struct {
Pickup string
Dropoff string
ETA time.Duration
}
// Interface exposed by the Driver service.
type Interface interface {
FindRoute(ctx context.Context, pickup, dropoff string) (*Route, error)
}

@ -0,0 +1,123 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route
import (
"context"
"encoding/json"
"math"
"math/rand"
"net/http"
"time"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/delay"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/httperr"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
"github.com/jaegertracing/jaeger/examples/hotrod/services/config"
)
// Server implements Route service
type Server struct {
hostPort string
tracer trace.TracerProvider
logger log.Factory
}
// NewServer creates a new route.Server
func NewServer(hostPort string, tracer trace.TracerProvider, logger log.Factory) *Server {
return &Server{
hostPort: hostPort,
tracer: tracer,
logger: logger,
}
}
// Run starts the Route server
func (s *Server) Run() error {
mux := s.createServeMux()
s.logger.Bg().Info("Starting", zap.String("address", "http://"+s.hostPort))
server := &http.Server{
Addr: s.hostPort,
Handler: mux,
ReadHeaderTimeout: 3 * time.Second,
}
return server.ListenAndServe()
}
func (s *Server) createServeMux() http.Handler {
mux := tracing.NewServeMux(false, s.tracer, s.logger)
mux.Handle("/route", http.HandlerFunc(s.route))
mux.Handle("/debug/vars", http.HandlerFunc(movedToFrontend))
mux.Handle("/metrics", http.HandlerFunc(movedToFrontend))
return mux
}
func movedToFrontend(w http.ResponseWriter, r *http.Request) {
http.Error(w, "endpoint moved to the frontend service", http.StatusNotFound)
}
func (s *Server) route(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s.logger.For(ctx).Info("HTTP request received", zap.String("method", r.Method), zap.Stringer("url", r.URL))
if err := r.ParseForm(); httperr.HandleError(w, err, http.StatusBadRequest) {
s.logger.For(ctx).Error("bad request", zap.Error(err))
return
}
pickup := r.Form.Get("pickup")
if pickup == "" {
http.Error(w, "Missing required 'pickup' parameter", http.StatusBadRequest)
return
}
dropoff := r.Form.Get("dropoff")
if dropoff == "" {
http.Error(w, "Missing required 'dropoff' parameter", http.StatusBadRequest)
return
}
response := computeRoute(ctx, pickup, dropoff)
data, err := json.Marshal(response)
if httperr.HandleError(w, err, http.StatusInternalServerError) {
s.logger.For(ctx).Error("cannot marshal response", zap.Error(err))
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
func computeRoute(ctx context.Context, pickup, dropoff string) *Route {
start := time.Now()
defer func() {
updateCalcStats(ctx, time.Since(start))
}()
// Simulate expensive calculation
delay.Sleep(config.RouteCalcDelay, config.RouteCalcDelayStdDev)
eta := math.Max(2, rand.NormFloat64()*3+5)
return &Route{
Pickup: pickup,
Dropoff: dropoff,
ETA: time.Duration(eta) * time.Minute,
}
}

@ -0,0 +1,53 @@
// Copyright (c) 2019 The Jaeger Authors.
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route
import (
"context"
"expvar"
"time"
"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing"
)
var (
routeCalcByCustomer = expvar.NewMap("route.calc.by.customer.sec")
routeCalcBySession = expvar.NewMap("route.calc.by.session.sec")
)
var stats = []struct {
expvar *expvar.Map
baggageKey string
}{
{
expvar: routeCalcByCustomer,
baggageKey: "customer",
},
{
expvar: routeCalcBySession,
baggageKey: "session",
},
}
func updateCalcStats(ctx context.Context, delay time.Duration) {
delaySec := float64(delay/time.Millisecond) / 1000.0
for _, s := range stats {
key := tracing.BaggageItem(ctx, s.baggageKey)
if key != "" {
s.expvar.AddFloat(key, delaySec)
}
}
}
Loading…
Cancel
Save