mirror of https://github.com/kubernetes/kops.git
otel tools: basic trace server for jaeger
This allows us to explore a trace file in jaeger.
This commit is contained in:
parent
e0a79e1bd1
commit
831352fbe7
|
@ -0,0 +1,42 @@
|
|||
# Copyright 2023 The Kubernetes 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.
|
||||
|
||||
.PHONY: protobuf
|
||||
protobuf:
|
||||
protoc --go_out=pb/ --go_opt=paths=source_relative --go-grpc_out=pb --go-grpc_opt=paths=source_relative \
|
||||
-I pb/ \
|
||||
pb/jaeger/api/v2/*.proto pb/jaeger/storage/v1/*.proto
|
||||
go run golang.org/x/tools/cmd/goimports@latest -w ./pb
|
||||
|
||||
.PHONY: wget
|
||||
wget:
|
||||
mkdir -p pb/jaeger/api/v2/
|
||||
wget -O pb/jaeger/api/v2/model.proto https://raw.githubusercontent.com/jaegertracing/jaeger-idl/main/proto/api_v2/model.proto
|
||||
mkdir -p pb/jaeger/storage/v1/
|
||||
wget -O pb/jaeger/storage/v1/storage.proto https://raw.githubusercontent.com/jaegertracing/jaeger/main/plugin/storage/grpc/proto/storage.proto
|
||||
# Remove gogoproto
|
||||
sed -i '/gogoproto/d' pb/jaeger/api/v2/model.proto
|
||||
sed -i 's@go_package@go_package="k8s.io/kops/tools/otel/traceserver/pb/jaeger/api/v2"; //@g' pb/jaeger/api/v2/model.proto
|
||||
sed -i '/gogoproto/d' pb/jaeger/storage/v1/storage.proto
|
||||
sed -i 's@go_package@go_package="k8s.io/kops/tools/otel/traceserver/pb/jaeger/storage/v1"; //@g' pb/jaeger/storage/v1/storage.proto
|
||||
sed -i 's@import "model.proto"@import "jaeger/api/v2/model.proto"@g' pb/jaeger/storage/v1/storage.proto
|
||||
# Remove empty [ ] directives (which span lines)
|
||||
cat pb/jaeger/api/v2/model.proto | \
|
||||
tr '\n' '~' | sed 's/\[\~[ ]*\]//g' | \
|
||||
tr '~' '\n' > pb/jaeger/api/v2/model.proto.out
|
||||
mv pb/jaeger/api/v2/model.proto.out pb/jaeger/api/v2/model.proto
|
||||
cat pb/jaeger/storage/v1/storage.proto | \
|
||||
tr '\n' '~' | sed 's/\[\~[ ]*\]//g' | \
|
||||
tr '~' '\n' > pb/jaeger/storage/v1/storage.proto.out
|
||||
mv pb/jaeger/storage/v1/storage.proto.out pb/jaeger/storage/v1/storage.proto
|
|
@ -0,0 +1,22 @@
|
|||
module k8s.io/kops/tools/otel/traceserver
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/proto/otlp v1.0.0
|
||||
google.golang.org/grpc v1.58.3
|
||||
google.golang.org/protobuf v1.31.0
|
||||
k8s.io/klog/v2 v2.100.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
|
||||
)
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
v11 "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
v1 "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"k8s.io/klog/v2"
|
||||
v2 "k8s.io/kops/tools/otel/traceserver/pb/jaeger/api/v2"
|
||||
storagev1 "k8s.io/kops/tools/otel/traceserver/pb/jaeger/storage/v1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(context.Background()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
listen := "127.0.0.1:12345"
|
||||
run := ""
|
||||
src := ""
|
||||
flag.StringVar(&src, "src", src, "tracefile to load")
|
||||
flag.StringVar(&listen, "listen", listen, "endpoint on which to serve grpc")
|
||||
flag.StringVar(&run, "run", run, "visualization program to run [jaeger]")
|
||||
klog.InitFlags(nil)
|
||||
flag.Parse()
|
||||
|
||||
if src == "" {
|
||||
return fmt.Errorf("--src is required")
|
||||
}
|
||||
|
||||
if run != "" {
|
||||
switch run {
|
||||
case "jaeger":
|
||||
go func() {
|
||||
opt := RunJaegerOptions{
|
||||
StorageServer: listen,
|
||||
}
|
||||
err := runJaeger(ctx, opt)
|
||||
if err != nil {
|
||||
klog.Warningf("error starting jaeger: %w", err)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
return fmt.Errorf("run=%q not known (valid values: jaeger)", run)
|
||||
}
|
||||
}
|
||||
|
||||
traceFilePath := src
|
||||
traceFile, err := ReadTraceFile(traceFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading %q: %w", traceFilePath, err)
|
||||
}
|
||||
|
||||
lis, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listening on %q: %w", listen, err)
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
traceFiles: []*TraceFile{traceFile},
|
||||
}
|
||||
grpcServer := grpc.NewServer()
|
||||
storagev1.RegisterPluginCapabilitiesServer(grpcServer, s)
|
||||
storagev1.RegisterSpanReaderPluginServer(grpcServer, s)
|
||||
storagev1.RegisterDependenciesReaderPluginServer(grpcServer, s)
|
||||
log.Printf("server listening at %v", lis.Addr())
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
return fmt.Errorf("serving %q: %w", listen, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
traceFiles []*TraceFile
|
||||
|
||||
storagev1.UnimplementedPluginCapabilitiesServer
|
||||
storagev1.UnimplementedSpanReaderPluginServer
|
||||
storagev1.UnimplementedDependenciesReaderPluginServer
|
||||
}
|
||||
|
||||
func (s *Server) Capabilities(ctx context.Context, req *storagev1.CapabilitiesRequest) (*storagev1.CapabilitiesResponse, error) {
|
||||
klog.V(2).Infof("Capabilities %v", prototext.Format(req))
|
||||
response := &storagev1.CapabilitiesResponse{
|
||||
ArchiveSpanReader: false,
|
||||
ArchiveSpanWriter: false,
|
||||
StreamingSpanWriter: false,
|
||||
}
|
||||
klog.V(4).Infof("<-Capabilities %v", prototext.Format(response))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetTrace(req *storagev1.GetTraceRequest, stream storagev1.SpanReaderPlugin_GetTraceServer) error {
|
||||
ctx := stream.Context()
|
||||
|
||||
klog.V(2).Infof("GetTrace %v", prototext.Format(req))
|
||||
|
||||
var opt FilterOptions
|
||||
|
||||
return s.visitSpans(ctx, opt, func(serviceName string, resourceSpans *v1.ResourceSpans) error {
|
||||
chunk := &storagev1.SpansResponseChunk{}
|
||||
for _, scopeSpan := range resourceSpans.ScopeSpans {
|
||||
for _, span := range scopeSpan.Spans {
|
||||
if !bytes.Equal(span.TraceId, req.TraceId) {
|
||||
continue
|
||||
}
|
||||
|
||||
out := convertToJaeger(serviceName, span)
|
||||
chunk.Spans = append(chunk.Spans, out)
|
||||
}
|
||||
}
|
||||
klog.V(4).Infof("<-GetTrace %v", prototext.Format(chunk))
|
||||
if err := stream.Send(chunk); err != nil {
|
||||
klog.Warningf("error sending chunk: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (s *Server) GetServices(ctx context.Context, req *storagev1.GetServicesRequest) (*storagev1.GetServicesResponse, error) {
|
||||
klog.V(2).Infof("GetServices %v", prototext.Format(req))
|
||||
|
||||
services := make(map[string]struct{})
|
||||
|
||||
for _, traceFile := range s.traceFiles {
|
||||
for _, data := range traceFile.data {
|
||||
// klog.Infof("data %v", prototext.Format(data))
|
||||
for _, span := range data.ResourceSpans {
|
||||
if span.Resource != nil {
|
||||
for _, attr := range span.Resource.Attributes {
|
||||
if attr.GetKey() == "service.name" {
|
||||
serviceName := attr.GetValue().GetStringValue()
|
||||
if serviceName != "" {
|
||||
services[serviceName] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response := &storagev1.GetServicesResponse{}
|
||||
|
||||
for k := range services {
|
||||
response.Services = append(response.Services, k)
|
||||
}
|
||||
klog.V(4).Infof("<-GetServices %v", prototext.Format(response))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetOperations(ctx context.Context, req *storagev1.GetOperationsRequest) (*storagev1.GetOperationsResponse, error) {
|
||||
klog.V(2).Infof("GetOperations %v", prototext.Format(req))
|
||||
|
||||
var opt FilterOptions
|
||||
opt.ServiceName = req.GetService()
|
||||
|
||||
operations := make(map[string]struct{})
|
||||
|
||||
if err := s.visitSpans(ctx, opt, func(serviceName string, resourceSpans *v1.ResourceSpans) error {
|
||||
for _, scopeSpan := range resourceSpans.ScopeSpans {
|
||||
for _, span := range scopeSpan.Spans {
|
||||
operations[span.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &storagev1.GetOperationsResponse{}
|
||||
for operation := range operations {
|
||||
response.Operations = append(response.Operations, &storagev1.Operation{
|
||||
Name: operation,
|
||||
SpanKind: "client", // TODO: How do we know?
|
||||
})
|
||||
}
|
||||
klog.V(4).Infof("<-GetOperations %v", prototext.Format(response))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Server) FindTraces(req *storagev1.FindTracesRequest, stream storagev1.SpanReaderPlugin_FindTracesServer) error {
|
||||
ctx := stream.Context()
|
||||
|
||||
klog.V(2).Infof("FindTraces %v", prototext.Format(req))
|
||||
|
||||
var opt FilterOptions
|
||||
opt.ServiceName = req.GetQuery().GetServiceName()
|
||||
|
||||
// Note that we must return these in order of traceid, because that is what jaeger expects:
|
||||
// https://github.com/jaegertracing/jaeger/blob/7aeb457c21eed28b58cd34021eff14727229aa69/plugin/storage/grpc/shared/grpc_client.go#L201-L217
|
||||
traces := make(map[string]*storagev1.SpansResponseChunk)
|
||||
if err := s.visitSpans(ctx, opt, func(serviceName string, resourceSpans *v1.ResourceSpans) error {
|
||||
for _, scopeSpan := range resourceSpans.ScopeSpans {
|
||||
for _, span := range scopeSpan.Spans {
|
||||
if operationName := req.GetQuery().GetOperationName(); operationName != "" {
|
||||
if operationName != span.Name {
|
||||
continue
|
||||
}
|
||||
}
|
||||
out := convertToJaeger(serviceName, span)
|
||||
|
||||
key := string(span.TraceId)
|
||||
trace := traces[key]
|
||||
if trace == nil {
|
||||
trace = &storagev1.SpansResponseChunk{}
|
||||
traces[key] = trace
|
||||
}
|
||||
trace.Spans = append(trace.Spans, out)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, trace := range traces {
|
||||
chunk := trace
|
||||
klog.V(4).Infof("<-FindTraces %v", prototext.Format(chunk))
|
||||
if err := stream.Send(chunk); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) FindTraceIDs(ctx context.Context, req *storagev1.FindTraceIDsRequest) (*storagev1.FindTraceIDsResponse, error) {
|
||||
klog.Warningf("FindTraceIDs not implemented: %v", prototext.Format(req))
|
||||
return nil, status.Errorf(codes.Unimplemented, "method FindTraceIDs not implemented")
|
||||
}
|
||||
|
||||
func (s *Server) GetDependencies(ctx context.Context, req *storagev1.GetDependenciesRequest) (*storagev1.GetDependenciesResponse, error) {
|
||||
klog.Warningf("GetDependencies not implemented: %v", prototext.Format(req))
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDependencies not implemented")
|
||||
}
|
||||
|
||||
type TraceFile struct {
|
||||
data []*coltracepb.ExportTraceServiceRequest
|
||||
}
|
||||
|
||||
type FilterOptions struct {
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
func (s *Server) visitSpans(ctx context.Context, opt FilterOptions, callback func(string, *v1.ResourceSpans) error) error {
|
||||
for _, traceFile := range s.traceFiles {
|
||||
if err := traceFile.visitSpans(ctx, opt, callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *TraceFile) visitSpans(ctx context.Context, opt FilterOptions, callback func(string, *v1.ResourceSpans) error) error {
|
||||
for _, data := range f.data {
|
||||
// klog.Infof("data %v", prototext.Format(data))
|
||||
for _, span := range data.ResourceSpans {
|
||||
serviceName := ""
|
||||
for _, attr := range span.GetResource().GetAttributes() {
|
||||
if attr.GetKey() == "service.name" {
|
||||
serviceName = attr.GetValue().GetStringValue()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if opt.ServiceName != "" && serviceName != opt.ServiceName {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := callback(serviceName, span); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadTraceFile(p string) (*TraceFile, error) {
|
||||
out := &TraceFile{}
|
||||
|
||||
r, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for {
|
||||
header := make([]byte, 16)
|
||||
if _, err := io.ReadFull(r, header); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("reading header: %w", err)
|
||||
}
|
||||
|
||||
payloadLength := binary.BigEndian.Uint32(header[0:4])
|
||||
//checksum := binary.BigEndian.Uint32(header[4:8])
|
||||
flags := binary.BigEndian.Uint32(header[8:12])
|
||||
typeCode := binary.BigEndian.Uint32(header[12:16])
|
||||
|
||||
// TODO: Verify checksum
|
||||
|
||||
if flags != 0 {
|
||||
return nil, fmt.Errorf("unexpected flags value %v", flags)
|
||||
}
|
||||
|
||||
// TODO: Sanity-check payloadLength
|
||||
|
||||
payload := make([]byte, payloadLength)
|
||||
if _, err := io.ReadFull(r, payload); err != nil {
|
||||
return nil, fmt.Errorf("reading payload: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Better typeCode parsing
|
||||
if typeCode == 1 {
|
||||
// TODO: process type definitions
|
||||
} else if typeCode == 32 {
|
||||
obj := &coltracepb.ExportTraceServiceRequest{}
|
||||
if err := proto.Unmarshal(payload, obj); err != nil {
|
||||
return nil, fmt.Errorf("parsing ExportTraceServiceRequest: %w", err)
|
||||
}
|
||||
out.data = append(out.data, obj)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unexpected typecode ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func startTimeForSpan(span *v1.Span) *timestamppb.Timestamp {
|
||||
nanos := span.StartTimeUnixNano
|
||||
|
||||
if nanos == 0 {
|
||||
return nil
|
||||
}
|
||||
secs := nanos / 1e9
|
||||
nanos -= secs * 1e9
|
||||
return ×tamppb.Timestamp{
|
||||
Seconds: int64(secs),
|
||||
Nanos: int32(nanos),
|
||||
}
|
||||
}
|
||||
|
||||
func durationForSpan(span *v1.Span) *durationpb.Duration {
|
||||
if span.EndTimeUnixNano == 0 || span.StartTimeUnixNano == 0 {
|
||||
return nil
|
||||
}
|
||||
nanos := span.EndTimeUnixNano - span.StartTimeUnixNano
|
||||
secs := nanos / 1e9
|
||||
nanos -= secs * 1e9
|
||||
return &durationpb.Duration{Seconds: int64(secs), Nanos: int32(nanos)}
|
||||
}
|
||||
|
||||
func convertToJaeger(serviceName string, span *v1.Span) *v2.Span {
|
||||
|
||||
out := &v2.Span{}
|
||||
out.TraceId = span.TraceId
|
||||
out.SpanId = span.SpanId
|
||||
out.OperationName = span.Name
|
||||
out.StartTime = startTimeForSpan(span)
|
||||
out.Duration = durationForSpan(span)
|
||||
out.Process = &v2.Process{
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
if span.ParentSpanId != nil {
|
||||
out.References = append(out.References, &v2.SpanRef{
|
||||
TraceId: span.TraceId,
|
||||
SpanId: span.ParentSpanId,
|
||||
RefType: v2.SpanRefType_CHILD_OF,
|
||||
})
|
||||
}
|
||||
// References []*SpanRef `protobuf:"bytes,4,rep,name=references,proto3" json:"references,omitempty"`
|
||||
// Flags uint32 `protobuf:"varint,5,opt,name=flags,proto3" json:"flags,omitempty"`
|
||||
// Tags []*KeyValue `protobuf:"bytes,8,rep,name=tags,proto3" json:"tags,omitempty"`
|
||||
// Logs []*Log `protobuf:"bytes,9,rep,name=logs,proto3" json:"logs,omitempty"`
|
||||
// Process *Process `protobuf:"bytes,10,opt,name=process,proto3" json:"process,omitempty"`
|
||||
// ProcessId string `protobuf:"bytes,11,opt,name=process_id,json=processId,proto3" json:"process_id,omitempty"`
|
||||
// Warnings []string `protobuf:"bytes,12,rep,name=warnings,proto3" json:"warnings,omitempty"`
|
||||
|
||||
for _, attr := range span.Attributes {
|
||||
tag := &v2.KeyValue{
|
||||
Key: attr.GetKey(),
|
||||
}
|
||||
switch v := attr.GetValue().Value.(type) {
|
||||
case *v11.AnyValue_StringValue:
|
||||
tag.VStr = v.StringValue
|
||||
case *v11.AnyValue_IntValue:
|
||||
tag.VInt64 = v.IntValue
|
||||
case *v11.AnyValue_ArrayValue:
|
||||
s, err := attributeValueAsString(attr.GetValue())
|
||||
if err != nil {
|
||||
klog.Warningf("error converting array value: %v", err)
|
||||
s = "<?error>"
|
||||
}
|
||||
tag.VStr = s
|
||||
default:
|
||||
klog.Warningf("unhandled attribute type %T", v)
|
||||
}
|
||||
out.Tags = append(out.Tags, tag)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func attributeValueAsString(v *v11.AnyValue) (string, error) {
|
||||
switch v := v.Value.(type) {
|
||||
case *v11.AnyValue_StringValue:
|
||||
return v.StringValue, nil
|
||||
case *v11.AnyValue_ArrayValue:
|
||||
var values []string
|
||||
for _, a := range v.ArrayValue.GetValues() {
|
||||
s, err := attributeValueAsString(a)
|
||||
if err != nil {
|
||||
klog.Warningf("error converting array value: %v", err)
|
||||
s = "<?error>"
|
||||
}
|
||||
values = append(values, s)
|
||||
}
|
||||
return "[" + strings.Join(values, ",") + "]", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unhandled attribute type %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// RunJaegerOptions are the options for runJaeger
|
||||
type RunJaegerOptions struct {
|
||||
StorageServer string
|
||||
}
|
||||
|
||||
// runJaeger starts the jaeger query & visualizer, binding to our storage server
|
||||
func runJaeger(ctx context.Context, opt RunJaegerOptions) error {
|
||||
jaegerURL := "http://127.0.0.1:16686/"
|
||||
|
||||
var jaeger *exec.Cmd
|
||||
{
|
||||
klog.Infof("starting jaeger")
|
||||
args := []string{
|
||||
"docker", "run", "--rm", "--network=host", "--name=jaeger",
|
||||
"-e=SPAN_STORAGE_TYPE=grpc-plugin",
|
||||
"jaegertracing/jaeger-query",
|
||||
"--grpc-storage.server=" + opt.StorageServer,
|
||||
}
|
||||
|
||||
c := exec.CommandContext(ctx, args[0], args[1:]...)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
if err := c.Start(); err != nil {
|
||||
return fmt.Errorf("starting jaeger in docker (%s): %w", strings.Join(args, " "), err)
|
||||
}
|
||||
jaeger = c
|
||||
}
|
||||
|
||||
{
|
||||
fmt.Fprintf(os.Stdout, "open browser to %s\n", jaegerURL)
|
||||
args := []string{"xdg-open", jaegerURL}
|
||||
c := exec.CommandContext(ctx, args[0], args[1:]...)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return fmt.Errorf("opening webbrowser (%s): %w", strings.Join(args, " "), err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := jaeger.Wait(); err != nil {
|
||||
return fmt.Errorf("waiting for jaeger to exit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue