parent
8b6b340e3c
commit
9cb7660b65
@ -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
|
||||
)
|
@ -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
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…
Reference in new issue