diff --git a/app/victoria-traces/main.go b/app/victoria-traces/main.go index 2ddb7fc16..9a0aae72e 100644 --- a/app/victoria-traces/main.go +++ b/app/victoria-traces/main.go @@ -15,6 +15,7 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil" "github.com/VictoriaMetrics/VictoriaMetrics/lib/pushmetrics" + "github.com/VictoriaMetrics/VictoriaTraces/app/victoria-traces/servicegraph" "github.com/VictoriaMetrics/VictoriaTraces/app/vtinsert" "github.com/VictoriaMetrics/VictoriaTraces/app/vtinsert/insertutil" "github.com/VictoriaMetrics/VictoriaTraces/app/vtselect" @@ -49,6 +50,9 @@ func main() { insertutil.SetLogRowsStorage(&vtstorage.Storage{}) vtinsert.Init() + // optional background task(s) + servicegraph.Init() + go httpserver.Serve(listenAddrs, requestHandler, httpserver.ServeOptions{ UseProxyProtocol: useProxyProtocol, }) @@ -66,6 +70,7 @@ func main() { } logger.Infof("successfully shut down the webservice in %.3f seconds", time.Since(startTime).Seconds()) + servicegraph.Stop() vtinsert.Stop() vtselect.Stop() vtstorage.Stop() diff --git a/app/victoria-traces/servicegraph/servicegraph.go b/app/victoria-traces/servicegraph/servicegraph.go new file mode 100644 index 000000000..60f57cad1 --- /dev/null +++ b/app/victoria-traces/servicegraph/servicegraph.go @@ -0,0 +1,101 @@ +package servicegraph + +import ( + "context" + "flag" + "time" + + "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" + + vtinsert "github.com/VictoriaMetrics/VictoriaTraces/app/vtinsert/opentelemetry" + vtselect "github.com/VictoriaMetrics/VictoriaTraces/app/vtselect/traces/query" + "github.com/VictoriaMetrics/VictoriaTraces/app/vtstorage" +) + +var ( + enableServiceGraphTask = flag.Bool("servicegraph.enableTask", false, "Whether to enable background task for generating service graph. It should only be enabled on VictoriaTraces single-node or vtstorage.") + serviceGraphTaskInterval = flag.Duration("servicegraph.taskInterval", time.Minute, "The background task interval for generating service graph data. It requires setting -servicegraph.enableTask=true.") + serviceGraphTaskTimeout = flag.Duration("servicegraph.taskTimeout", 30*time.Second, "The background task timeout duration for generating service graph data. It requires setting -servicegraph.enableTask=true.") + serviceGraphTaskLookbehind = flag.Duration("servicegraph.taskLookbehind", time.Minute, "The lookbehind window for each time service graph background task run. It requires setting -servicegraph.enableTask=true.") + serviceGraphTaskLimit = flag.Uint64("servicegraph.taskLimit", 1000, "How many service graph relations each task could fetch for each tenant. It requires setting -servicegraph.enableTask=true.") +) + +var ( + sgt *serviceGraphTask +) + +func Init() { + if *enableServiceGraphTask { + sgt = newServiceGraphTask() + sgt.Start() + } +} + +func Stop() { + if *enableServiceGraphTask { + sgt.Stop() + } +} + +type serviceGraphTask struct { + stopCh chan struct{} +} + +func newServiceGraphTask() *serviceGraphTask { + return &serviceGraphTask{ + stopCh: make(chan struct{}), + } +} + +func (sgt *serviceGraphTask) Start() { + logger.Infof("starting servicegraph background task, interval: %v, lookbehind: %v", *serviceGraphTaskInterval, *serviceGraphTaskLookbehind) + go func() { + ticker := time.NewTicker(*serviceGraphTaskInterval) + defer ticker.Stop() + + for { + select { + case <-sgt.stopCh: + return + case <-ticker.C: + ctx, cancelFunc := context.WithTimeout(context.Background(), *serviceGraphTaskTimeout) + GenerateServiceGraphTimeRange(ctx) + cancelFunc() + } + } + }() +} + +func (sgt *serviceGraphTask) Stop() { + close(sgt.stopCh) +} + +func GenerateServiceGraphTimeRange(ctx context.Context) { + endTime := time.Now().Truncate(*serviceGraphTaskInterval) + startTime := endTime.Add(-*serviceGraphTaskLookbehind) + + tenantIDs, err := vtstorage.GetTenantIDsByTimeRange(ctx, startTime.UnixNano(), endTime.UnixNano()) + if err != nil { + logger.Errorf("cannot get tenant ids: %s", err) + return + } + + // query and persist operations are executed sequentially, which helps not to consume excessive resources. + for _, tenantID := range tenantIDs { + // query service graph relations + rows, err := vtselect.GetServiceGraphTimeRange(ctx, tenantID, startTime, endTime, *serviceGraphTaskLimit) + if err != nil { + logger.Errorf("cannot get service graph for time range [%d, %d]: %s", startTime.Unix(), endTime.Unix(), err) + return + } + if len(rows) == 0 { + return + } + + // persist service graph relations + err = vtinsert.PersistServiceGraph(ctx, tenantID, rows, endTime) + if err != nil { + logger.Errorf("cannot presist service graph for time range [%d, %d]: %s", startTime.Unix(), endTime.Unix(), err) + } + } +} diff --git a/app/vtgen/main.go b/app/vtgen/main.go index db2a33e33..c0dbea0dd 100644 --- a/app/vtgen/main.go +++ b/app/vtgen/main.go @@ -68,6 +68,7 @@ func main() { // The traceIDMap recorded old traceID->new traceID. // Spans with same old traceID should be replaced with same new traceID. traceIDMap := make(map[string]string) + spanIDMap := make(map[string]string) // The timeOffset is the time offset of span timestamp and current timestamp. // All spans' timestamp should be increased by this offset. @@ -115,6 +116,26 @@ func main() { } } + // replace SpanID + if sid, ok := spanIDMap[sp.SpanID]; ok { + sp.SpanID = sid + } else { + spanID := generateSpanID() + oldSpanID := sp.SpanID + sp.SpanID = spanID + spanIDMap[oldSpanID] = spanID + } + + // replace parentSpanID + if sid, ok := spanIDMap[sp.ParentSpanID]; ok { + sp.ParentSpanID = sid + } else { + parentSpanID := generateSpanID() + oldParentSpanID := sp.ParentSpanID + sp.ParentSpanID = parentSpanID + spanIDMap[oldParentSpanID] = parentSpanID + } + // adjust the timestamp of the span. sp.StartTimeUnixNano = sp.StartTimeUnixNano + timeOffset sp.EndTimeUnixNano = sp.EndTimeUnixNano + timeOffset + uint64(rand.Int63n(100000000)) @@ -198,12 +219,27 @@ func loadTestData() [][]byte { return bodyList } +var traceIDMutex sync.Mutex + func generateTraceID() string { + traceIDMutex.Lock() + defer traceIDMutex.Unlock() + h := md5.New() h.Write([]byte(strconv.FormatInt(time.Now().UnixNano(), 10))) return hex.EncodeToString(h.Sum(nil)) } +var spanIDMutex sync.Mutex + +func generateSpanID() string { + spanIDMutex.Lock() + defer spanIDMutex.Unlock() + h := md5.New() + h.Write([]byte(strconv.FormatInt(time.Now().UnixNano(), 10))) + return hex.EncodeToString(h.Sum(nil))[:16] +} + // readWrite Does the following: // 1. read request body binary files like `1.bin`, `2.bin` and puts them into `BodyList`. // 2. encode and compress the `BodyList` into `[]byte`. diff --git a/app/vtinsert/opentelemetry/opentelemetry.go b/app/vtinsert/opentelemetry/opentelemetry.go index c6fc45233..e011df515 100644 --- a/app/vtinsert/opentelemetry/opentelemetry.go +++ b/app/vtinsert/opentelemetry/opentelemetry.go @@ -1,6 +1,7 @@ package opentelemetry import ( + "context" "fmt" "net/http" "strconv" @@ -284,3 +285,21 @@ func appendKeyValuesWithPrefixSuffix(fields []logstorage.Field, kvs []*otelpb.Ke } return fields } + +func PersistServiceGraph(ctx context.Context, tenantID logstorage.TenantID, fields [][]logstorage.Field, timestamp time.Time) error { + cp := insertutil.CommonParams{ + TenantID: tenantID, + TimeFields: []string{"_time"}, + } + lmp := cp.NewLogMessageProcessor("internalinsert_servicegraph", false) + + for _, row := range fields { + f := append(row, logstorage.Field{ + Name: "_msg", + Value: "-", + }) + lmp.AddRow(timestamp.UnixNano(), f, []logstorage.Field{{Name: otelpb.ServiceGraphStreamName, Value: "-"}}) + } + lmp.MustClose() + return nil +} diff --git a/app/vtselect/traces/jaeger/jaeger.go b/app/vtselect/traces/jaeger/jaeger.go index fe0a3d900..a117ab2fc 100644 --- a/app/vtselect/traces/jaeger/jaeger.go +++ b/app/vtselect/traces/jaeger/jaeger.go @@ -73,8 +73,7 @@ func RequestHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) return true } else if path == "/select/jaeger/api/dependencies" { jaegerDependenciesRequests.Inc() - // todo it require additional component to calculate the dependency graph. not implemented yet. - httpserver.Errorf(w, r, "/api/dependencies API is not supported yet.") + processGetDependenciesRequest(ctx, w, r) jaegerDependenciesDuration.UpdateDuration(startTime) return true } @@ -401,3 +400,91 @@ func hashProcess(process process) uint64 { hashpool.Put(d) return h } + +// processGetDependenciesRequest handle the Jaeger /api/dependencies API request. +func processGetDependenciesRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) { + cp, err := query.GetCommonParams(r) + if err != nil { + httpserver.Errorf(w, r, "incorrect query params: %s", err) + return + } + + param, err := parseJaegerDependenciesQueryParam(ctx, r) + if err != nil { + httpserver.Errorf(w, r, "incorrect dependencies query params: %s", err) + return + } + + rows, err := query.GetServiceGraphList(ctx, cp, param) + if err != nil { + httpserver.Errorf(w, r, "get dependencies error: %s", err) + return + } + + if len(rows) == 0 { + // Write empty results + w.Header().Set("Content-Type", "application/json") + WriteGetDependenciesResponse(w, nil) + return + } + + dependencies := make([]*dependencyLink, 0) + for _, row := range rows { + dependency := &dependencyLink{} + for _, f := range row.Fields { + switch f.Name { + case "parent": + dependency.parent = f.Value + case "child": + dependency.child = f.Value + case "callCount": + dependency.callCount, err = strconv.ParseUint(f.Value, 10, 64) + if err != nil { + logger.Errorf("cannot parse callCount [%s]: %s", f.Value, err) + continue + } + } + } + if dependency.parent != "" && dependency.child != "" && dependency.callCount > 0 { + dependencies = append(dependencies, dependency) + } + } + + // Write results + w.Header().Set("Content-Type", "application/json") + WriteGetDependenciesResponse(w, dependencies) +} + +// parseJaegerDependenciesQueryParam parse Jaeger request to unified ServiceGraphQueryParameters. +func parseJaegerDependenciesQueryParam(_ context.Context, r *http.Request) (*query.ServiceGraphQueryParameters, error) { + var err error + + // default params + p := &query.ServiceGraphQueryParameters{ + EndTs: time.Now(), + Lookback: time.Hour, + } + q := r.URL.Query() + + endTs := q.Get("endTs") + if endTs != "" { + unixMilli, err := strconv.ParseInt(endTs, 10, 64) + if err != nil { + return nil, fmt.Errorf("cannot parse endTs [%s]: %w", endTs, err) + } + p.EndTs = time.UnixMilli(unixMilli) + } + + lookback := q.Get("lookback") + if lookback != "" { + if strings.TrimLeft(lookback, "0123456789") == "" { + lookback += "ms" + } + p.Lookback, err = time.ParseDuration(lookback) + if err != nil { + return nil, fmt.Errorf("cannot parse lookback [%s]: %w", lookback, err) + } + } + + return p, nil +} diff --git a/app/vtselect/traces/jaeger/jaeger.qtpl b/app/vtselect/traces/jaeger/jaeger.qtpl index ab129dbe4..d816c3cdc 100644 --- a/app/vtselect/traces/jaeger/jaeger.qtpl +++ b/app/vtselect/traces/jaeger/jaeger.qtpl @@ -63,6 +63,31 @@ } {% endfunc %} +{% func GetDependenciesResponse(dependencies []*dependencyLink) %} +{ + "data":[ + {% if len(dependencies) > 0 %} + {%= dependencyJson(dependencies[0]) %} + {% for _, dependency := range dependencies[1:] %} + ,{%= dependencyJson(dependency) %} + {% endfor %} + {% endif %} + ], + "errors": null, + "limit": 0, + "offset": 0, + "total": {%d= len(dependencies) %} +} +{% endfunc %} + +{% func dependencyJson(dependency *dependencyLink) %} +{ + "parent": {%q= dependency.parent %}, + "child": {%q= dependency.child %}, + "callCount": {%dul= dependency.callCount %} +} +{% endfunc %} + {% func traceJson(trace *trace) %} { "processes": { diff --git a/app/vtselect/traces/jaeger/jaeger.qtpl.go b/app/vtselect/traces/jaeger/jaeger.qtpl.go index 51318059c..e15475b8f 100644 --- a/app/vtselect/traces/jaeger/jaeger.qtpl.go +++ b/app/vtselect/traces/jaeger/jaeger.qtpl.go @@ -1,570 +1,668 @@ // Code generated by qtc from "jaeger.qtpl". DO NOT EDIT. // See https://github.com/valyala/quicktemplate for details. -//line app/vtselect/traces/jaeger/jaeger.qtpl:1 +//line jaeger.qtpl:1 package jaeger -//line app/vtselect/traces/jaeger/jaeger.qtpl:1 +//line jaeger.qtpl:1 import ( "sort" ) -//line app/vtselect/traces/jaeger/jaeger.qtpl:7 +//line jaeger.qtpl:7 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line app/vtselect/traces/jaeger/jaeger.qtpl:7 +//line jaeger.qtpl:7 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line app/vtselect/traces/jaeger/jaeger.qtpl:7 +//line jaeger.qtpl:7 func StreamGetServicesResponse(qw422016 *qt422016.Writer, serviceList []string) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:7 +//line jaeger.qtpl:7 qw422016.N().S(`{`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:10 +//line jaeger.qtpl:10 sort.Slice(serviceList, func(i, j int) bool { return serviceList[i] < serviceList[j] }) -//line app/vtselect/traces/jaeger/jaeger.qtpl:11 +//line jaeger.qtpl:11 qw422016.N().S(`"data":[`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:13 +//line jaeger.qtpl:13 if len(serviceList) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:14 +//line jaeger.qtpl:14 qw422016.N().Q(serviceList[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:15 +//line jaeger.qtpl:15 for _, service := range serviceList[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:15 +//line jaeger.qtpl:15 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:16 +//line jaeger.qtpl:16 qw422016.N().Q(service) -//line app/vtselect/traces/jaeger/jaeger.qtpl:17 +//line jaeger.qtpl:17 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:18 +//line jaeger.qtpl:18 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:18 +//line jaeger.qtpl:18 qw422016.N().S(`],"errors": null,"limit": 0,"offset": 0,"total":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:23 +//line jaeger.qtpl:23 qw422016.N().D(len(serviceList)) -//line app/vtselect/traces/jaeger/jaeger.qtpl:23 +//line jaeger.qtpl:23 qw422016.N().S(`}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 func WriteGetServicesResponse(qq422016 qtio422016.Writer, serviceList []string) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 StreamGetServicesResponse(qw422016, serviceList) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 func GetServicesResponse(serviceList []string) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 WriteGetServicesResponse(qb422016, serviceList) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:25 +//line jaeger.qtpl:25 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:27 +//line jaeger.qtpl:27 func StreamGetOperationsResponse(qw422016 *qt422016.Writer, operationList []string) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:27 +//line jaeger.qtpl:27 qw422016.N().S(`{`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:30 +//line jaeger.qtpl:30 sort.Slice(operationList, func(i, j int) bool { return operationList[i] < operationList[j] }) -//line app/vtselect/traces/jaeger/jaeger.qtpl:31 +//line jaeger.qtpl:31 qw422016.N().S(`"data":[`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:33 +//line jaeger.qtpl:33 if len(operationList) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:34 +//line jaeger.qtpl:34 qw422016.N().Q(operationList[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:35 +//line jaeger.qtpl:35 for _, operation := range operationList[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:35 +//line jaeger.qtpl:35 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:36 +//line jaeger.qtpl:36 qw422016.N().Q(operation) -//line app/vtselect/traces/jaeger/jaeger.qtpl:37 +//line jaeger.qtpl:37 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:38 +//line jaeger.qtpl:38 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:38 +//line jaeger.qtpl:38 qw422016.N().S(`],"errors": null,"limit": 0,"offset": 0,"total":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:43 +//line jaeger.qtpl:43 qw422016.N().D(len(operationList)) -//line app/vtselect/traces/jaeger/jaeger.qtpl:43 +//line jaeger.qtpl:43 qw422016.N().S(`}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 func WriteGetOperationsResponse(qq422016 qtio422016.Writer, operationList []string) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 StreamGetOperationsResponse(qw422016, operationList) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 func GetOperationsResponse(operationList []string) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 WriteGetOperationsResponse(qb422016, operationList) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:45 +//line jaeger.qtpl:45 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:47 +//line jaeger.qtpl:47 func StreamGetTracesResponse(qw422016 *qt422016.Writer, traces []*trace) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:47 +//line jaeger.qtpl:47 qw422016.N().S(`{"data":[`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:50 +//line jaeger.qtpl:50 if len(traces) > 0 && len(traces[0].spans) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:51 +//line jaeger.qtpl:51 streamtraceJson(qw422016, traces[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:52 +//line jaeger.qtpl:52 for _, trace := range traces[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:53 +//line jaeger.qtpl:53 if len(trace.spans) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:53 +//line jaeger.qtpl:53 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:54 +//line jaeger.qtpl:54 streamtraceJson(qw422016, trace) -//line app/vtselect/traces/jaeger/jaeger.qtpl:55 +//line jaeger.qtpl:55 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:56 +//line jaeger.qtpl:56 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:57 +//line jaeger.qtpl:57 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:57 +//line jaeger.qtpl:57 qw422016.N().S(`],"errors": null,"limit": 0,"offset": 0,"total":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:62 +//line jaeger.qtpl:62 qw422016.N().D(len(traces)) -//line app/vtselect/traces/jaeger/jaeger.qtpl:62 +//line jaeger.qtpl:62 qw422016.N().S(`}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 func WriteGetTracesResponse(qq422016 qtio422016.Writer, traces []*trace) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 StreamGetTracesResponse(qw422016, traces) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 func GetTracesResponse(traces []*trace) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 WriteGetTracesResponse(qb422016, traces) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:64 +//line jaeger.qtpl:64 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:66 +//line jaeger.qtpl:66 +func StreamGetDependenciesResponse(qw422016 *qt422016.Writer, dependencies []*dependencyLink) { +//line jaeger.qtpl:66 + qw422016.N().S(`{"data":[`) +//line jaeger.qtpl:69 + if len(dependencies) > 0 { +//line jaeger.qtpl:70 + streamdependencyJson(qw422016, dependencies[0]) +//line jaeger.qtpl:71 + for _, dependency := range dependencies[1:] { +//line jaeger.qtpl:71 + qw422016.N().S(`,`) +//line jaeger.qtpl:72 + streamdependencyJson(qw422016, dependency) +//line jaeger.qtpl:73 + } +//line jaeger.qtpl:74 + } +//line jaeger.qtpl:74 + qw422016.N().S(`],"errors": null,"limit": 0,"offset": 0,"total":`) +//line jaeger.qtpl:79 + qw422016.N().D(len(dependencies)) +//line jaeger.qtpl:79 + qw422016.N().S(`}`) +//line jaeger.qtpl:81 +} + +//line jaeger.qtpl:81 +func WriteGetDependenciesResponse(qq422016 qtio422016.Writer, dependencies []*dependencyLink) { +//line jaeger.qtpl:81 + qw422016 := qt422016.AcquireWriter(qq422016) +//line jaeger.qtpl:81 + StreamGetDependenciesResponse(qw422016, dependencies) +//line jaeger.qtpl:81 + qt422016.ReleaseWriter(qw422016) +//line jaeger.qtpl:81 +} + +//line jaeger.qtpl:81 +func GetDependenciesResponse(dependencies []*dependencyLink) string { +//line jaeger.qtpl:81 + qb422016 := qt422016.AcquireByteBuffer() +//line jaeger.qtpl:81 + WriteGetDependenciesResponse(qb422016, dependencies) +//line jaeger.qtpl:81 + qs422016 := string(qb422016.B) +//line jaeger.qtpl:81 + qt422016.ReleaseByteBuffer(qb422016) +//line jaeger.qtpl:81 + return qs422016 +//line jaeger.qtpl:81 +} + +//line jaeger.qtpl:83 +func streamdependencyJson(qw422016 *qt422016.Writer, dependency *dependencyLink) { +//line jaeger.qtpl:83 + qw422016.N().S(`{"parent":`) +//line jaeger.qtpl:85 + qw422016.N().Q(dependency.parent) +//line jaeger.qtpl:85 + qw422016.N().S(`,"child":`) +//line jaeger.qtpl:86 + qw422016.N().Q(dependency.child) +//line jaeger.qtpl:86 + qw422016.N().S(`,"callCount":`) +//line jaeger.qtpl:87 + qw422016.N().DUL(dependency.callCount) +//line jaeger.qtpl:87 + qw422016.N().S(`}`) +//line jaeger.qtpl:89 +} + +//line jaeger.qtpl:89 +func writedependencyJson(qq422016 qtio422016.Writer, dependency *dependencyLink) { +//line jaeger.qtpl:89 + qw422016 := qt422016.AcquireWriter(qq422016) +//line jaeger.qtpl:89 + streamdependencyJson(qw422016, dependency) +//line jaeger.qtpl:89 + qt422016.ReleaseWriter(qw422016) +//line jaeger.qtpl:89 +} + +//line jaeger.qtpl:89 +func dependencyJson(dependency *dependencyLink) string { +//line jaeger.qtpl:89 + qb422016 := qt422016.AcquireByteBuffer() +//line jaeger.qtpl:89 + writedependencyJson(qb422016, dependency) +//line jaeger.qtpl:89 + qs422016 := string(qb422016.B) +//line jaeger.qtpl:89 + qt422016.ReleaseByteBuffer(qb422016) +//line jaeger.qtpl:89 + return qs422016 +//line jaeger.qtpl:89 +} + +//line jaeger.qtpl:91 func streamtraceJson(qw422016 *qt422016.Writer, trace *trace) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:66 +//line jaeger.qtpl:91 qw422016.N().S(`{"processes": {`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:69 +//line jaeger.qtpl:94 if len(trace.processMap) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:70 +//line jaeger.qtpl:95 qw422016.N().Q(trace.processMap[0].processID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:70 +//line jaeger.qtpl:95 qw422016.N().S(`:`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:70 +//line jaeger.qtpl:95 streamprocessJson(qw422016, trace.processMap[0].process) -//line app/vtselect/traces/jaeger/jaeger.qtpl:71 +//line jaeger.qtpl:96 for _, v := range trace.processMap[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:71 +//line jaeger.qtpl:96 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:72 +//line jaeger.qtpl:97 qw422016.N().Q(v.processID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:72 +//line jaeger.qtpl:97 qw422016.N().S(`:`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:72 +//line jaeger.qtpl:97 streamprocessJson(qw422016, v.process) -//line app/vtselect/traces/jaeger/jaeger.qtpl:73 +//line jaeger.qtpl:98 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:74 +//line jaeger.qtpl:99 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:74 +//line jaeger.qtpl:99 qw422016.N().S(`},"spans": [`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:77 +//line jaeger.qtpl:102 if len(trace.spans) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:78 +//line jaeger.qtpl:103 streamspanJson(qw422016, trace.spans[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:79 +//line jaeger.qtpl:104 for _, v := range trace.spans[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:79 +//line jaeger.qtpl:104 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:80 +//line jaeger.qtpl:105 streamspanJson(qw422016, v) -//line app/vtselect/traces/jaeger/jaeger.qtpl:81 +//line jaeger.qtpl:106 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:82 +//line jaeger.qtpl:107 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:82 +//line jaeger.qtpl:107 qw422016.N().S(`],"traceID":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:84 +//line jaeger.qtpl:109 qw422016.N().Q(trace.spans[0].traceID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:84 +//line jaeger.qtpl:109 qw422016.N().S(`,"warnings": null}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 func writetraceJson(qq422016 qtio422016.Writer, trace *trace) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 streamtraceJson(qw422016, trace) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 func traceJson(trace *trace) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 writetraceJson(qb422016, trace) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:87 +//line jaeger.qtpl:112 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:89 +//line jaeger.qtpl:114 func streamprocessJson(qw422016 *qt422016.Writer, process process) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:89 +//line jaeger.qtpl:114 qw422016.N().S(`{"serviceName":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:91 +//line jaeger.qtpl:116 qw422016.N().Q(process.serviceName) -//line app/vtselect/traces/jaeger/jaeger.qtpl:91 +//line jaeger.qtpl:116 qw422016.N().S(`,"tags": [`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:93 +//line jaeger.qtpl:118 if len(process.tags) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:94 +//line jaeger.qtpl:119 streamtagJson(qw422016, process.tags[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:95 +//line jaeger.qtpl:120 for _, v := range process.tags[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:95 +//line jaeger.qtpl:120 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:96 +//line jaeger.qtpl:121 streamtagJson(qw422016, v) -//line app/vtselect/traces/jaeger/jaeger.qtpl:97 +//line jaeger.qtpl:122 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:98 +//line jaeger.qtpl:123 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:98 +//line jaeger.qtpl:123 qw422016.N().S(`]}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 func writeprocessJson(qq422016 qtio422016.Writer, process process) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 streamprocessJson(qw422016, process) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 func processJson(process process) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 writeprocessJson(qb422016, process) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:101 +//line jaeger.qtpl:126 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:103 +//line jaeger.qtpl:128 func streamspanJson(qw422016 *qt422016.Writer, span *span) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:103 +//line jaeger.qtpl:128 qw422016.N().S(`{"duration":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:105 +//line jaeger.qtpl:130 qw422016.N().DL(span.duration) -//line app/vtselect/traces/jaeger/jaeger.qtpl:105 +//line jaeger.qtpl:130 qw422016.N().S(`,"logs":[`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:107 +//line jaeger.qtpl:132 if len(span.logs) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:108 +//line jaeger.qtpl:133 streamlogJson(qw422016, span.logs[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:109 +//line jaeger.qtpl:134 for _, v := range span.logs[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:109 +//line jaeger.qtpl:134 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:110 +//line jaeger.qtpl:135 streamlogJson(qw422016, v) -//line app/vtselect/traces/jaeger/jaeger.qtpl:111 +//line jaeger.qtpl:136 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:112 +//line jaeger.qtpl:137 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:112 +//line jaeger.qtpl:137 qw422016.N().S(`],"operationName":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:114 +//line jaeger.qtpl:139 qw422016.N().Q(span.operationName) -//line app/vtselect/traces/jaeger/jaeger.qtpl:114 +//line jaeger.qtpl:139 qw422016.N().S(`,"processID":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:115 +//line jaeger.qtpl:140 qw422016.N().Q(span.processID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:115 +//line jaeger.qtpl:140 qw422016.N().S(`,"references": [`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:117 +//line jaeger.qtpl:142 if len(span.references) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:118 +//line jaeger.qtpl:143 streamspanRefJson(qw422016, span.references[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:119 +//line jaeger.qtpl:144 for _, v := range span.references[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:119 +//line jaeger.qtpl:144 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:120 +//line jaeger.qtpl:145 streamspanRefJson(qw422016, v) -//line app/vtselect/traces/jaeger/jaeger.qtpl:121 +//line jaeger.qtpl:146 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:122 +//line jaeger.qtpl:147 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:122 +//line jaeger.qtpl:147 qw422016.N().S(`],"spanID":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:124 +//line jaeger.qtpl:149 qw422016.N().Q(span.spanID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:124 +//line jaeger.qtpl:149 qw422016.N().S(`,"startTime":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:125 +//line jaeger.qtpl:150 qw422016.N().DL(span.startTime) -//line app/vtselect/traces/jaeger/jaeger.qtpl:125 +//line jaeger.qtpl:150 qw422016.N().S(`,"tags": [`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:127 +//line jaeger.qtpl:152 if len(span.tags) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:128 +//line jaeger.qtpl:153 streamtagJson(qw422016, span.tags[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:129 +//line jaeger.qtpl:154 for _, v := range span.tags[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:129 +//line jaeger.qtpl:154 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:130 +//line jaeger.qtpl:155 streamtagJson(qw422016, v) -//line app/vtselect/traces/jaeger/jaeger.qtpl:131 +//line jaeger.qtpl:156 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:132 +//line jaeger.qtpl:157 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:132 +//line jaeger.qtpl:157 qw422016.N().S(`],"traceID":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:134 +//line jaeger.qtpl:159 qw422016.N().Q(span.traceID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:134 +//line jaeger.qtpl:159 qw422016.N().S(`,"warnings":null}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 func writespanJson(qq422016 qtio422016.Writer, span *span) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 streamspanJson(qw422016, span) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 func spanJson(span *span) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 writespanJson(qb422016, span) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:137 +//line jaeger.qtpl:162 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:139 +//line jaeger.qtpl:164 func streamtagJson(qw422016 *qt422016.Writer, tag keyValue) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:139 +//line jaeger.qtpl:164 qw422016.N().S(`{"key":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:141 +//line jaeger.qtpl:166 qw422016.N().Q(tag.key) -//line app/vtselect/traces/jaeger/jaeger.qtpl:141 +//line jaeger.qtpl:166 qw422016.N().S(`,"type":"string","value":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:143 +//line jaeger.qtpl:168 qw422016.N().Q(tag.vStr) -//line app/vtselect/traces/jaeger/jaeger.qtpl:143 +//line jaeger.qtpl:168 qw422016.N().S(`}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 func writetagJson(qq422016 qtio422016.Writer, tag keyValue) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 streamtagJson(qw422016, tag) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 func tagJson(tag keyValue) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 writetagJson(qb422016, tag) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:145 +//line jaeger.qtpl:170 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:147 +//line jaeger.qtpl:172 func streamlogJson(qw422016 *qt422016.Writer, l log) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:147 +//line jaeger.qtpl:172 qw422016.N().S(`{"timestamp":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:149 +//line jaeger.qtpl:174 qw422016.N().DL(l.timestamp) -//line app/vtselect/traces/jaeger/jaeger.qtpl:149 +//line jaeger.qtpl:174 qw422016.N().S(`,"fields":[`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:151 +//line jaeger.qtpl:176 if len(l.fields) > 0 { -//line app/vtselect/traces/jaeger/jaeger.qtpl:152 +//line jaeger.qtpl:177 streamtagJson(qw422016, l.fields[0]) -//line app/vtselect/traces/jaeger/jaeger.qtpl:153 +//line jaeger.qtpl:178 for _, v := range l.fields[1:] { -//line app/vtselect/traces/jaeger/jaeger.qtpl:153 +//line jaeger.qtpl:178 qw422016.N().S(`,`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:154 +//line jaeger.qtpl:179 streamtagJson(qw422016, v) -//line app/vtselect/traces/jaeger/jaeger.qtpl:155 +//line jaeger.qtpl:180 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:156 +//line jaeger.qtpl:181 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:156 +//line jaeger.qtpl:181 qw422016.N().S(`]}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 func writelogJson(qq422016 qtio422016.Writer, l log) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 streamlogJson(qw422016, l) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 func logJson(l log) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 writelogJson(qb422016, l) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:159 +//line jaeger.qtpl:184 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:161 +//line jaeger.qtpl:186 func streamspanRefJson(qw422016 *qt422016.Writer, ref spanRef) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:161 +//line jaeger.qtpl:186 qw422016.N().S(`{"refType":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:163 +//line jaeger.qtpl:188 qw422016.N().Q(ref.refType) -//line app/vtselect/traces/jaeger/jaeger.qtpl:163 +//line jaeger.qtpl:188 qw422016.N().S(`,"spanID":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:164 +//line jaeger.qtpl:189 qw422016.N().Q(ref.spanID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:164 +//line jaeger.qtpl:189 qw422016.N().S(`,"traceID":`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:165 +//line jaeger.qtpl:190 qw422016.N().Q(ref.traceID) -//line app/vtselect/traces/jaeger/jaeger.qtpl:165 +//line jaeger.qtpl:190 qw422016.N().S(`}`) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 func writespanRefJson(qq422016 qtio422016.Writer, ref spanRef) { -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 streamspanRefJson(qw422016, ref) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 } -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 func spanRefJson(ref spanRef) string { -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 writespanRefJson(qb422016, ref) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 qs422016 := string(qb422016.B) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 return qs422016 -//line app/vtselect/traces/jaeger/jaeger.qtpl:167 +//line jaeger.qtpl:192 } diff --git a/app/vtselect/traces/jaeger/model.go b/app/vtselect/traces/jaeger/model.go index 735cdd8f6..18c1afe20 100644 --- a/app/vtselect/traces/jaeger/model.go +++ b/app/vtselect/traces/jaeger/model.go @@ -56,6 +56,12 @@ type log struct { fields []keyValue } +type dependencyLink struct { + parent string + child string + callCount uint64 +} + // since Jaeger renamed some fields in OpenTelemetry // into other span attributes during query, the following map // is created to translate the span attributes filter into the diff --git a/app/vtselect/traces/query/query.go b/app/vtselect/traces/query/query.go index 54dd8e0ef..f766e381a 100644 --- a/app/vtselect/traces/query/query.go +++ b/app/vtselect/traces/query/query.go @@ -566,3 +566,178 @@ func checkTraceIDList(traceIDList []string) []string { } return result } + +type ServiceGraphQueryParameters struct { + EndTs time.Time + Lookback time.Duration +} + +// GetServiceGraphList returns service dependencies graph edges (parent, child, callCount) in []*Row format. +// +// TODO: currently this function can only handle request from Jaeger dependencies API. Since Tempo provides similar service graph +// feature, it would be great to add support for Tempo service graph API as well. +func GetServiceGraphList(ctx context.Context, cp *CommonParams, param *ServiceGraphQueryParameters) ([]*Row, error) { + // {trace_service_graph_stream="-"} | fields parent, child, callCount | stats by (parent, child) sum(callCount) as callCount + qStr := fmt.Sprintf(`{%s="-"} | fields %s, %s, %s | stats by (%s, %s) sum(%s) as %s`, + otelpb.ServiceGraphStreamName, + otelpb.ServiceGraphParentFieldName, + otelpb.ServiceGraphChildFieldName, + otelpb.ServiceGraphCallCountFieldName, + otelpb.ServiceGraphParentFieldName, + otelpb.ServiceGraphChildFieldName, + otelpb.ServiceGraphCallCountFieldName, + otelpb.ServiceGraphCallCountFieldName, + ) + startTime := param.EndTs.Add(-param.Lookback).UnixNano() + endTime := param.EndTs.UnixNano() + q, err := logstorage.ParseQueryAtTimestamp(qStr, endTime) + if err != nil { + return nil, fmt.Errorf("cannot parse query [%s]: %s", qStr, err) + } + q.AddTimeFilter(startTime, endTime) + + cp.Query = q + qctx := cp.NewQueryContext(ctx) + + var rowsLock sync.Mutex + var rows []*Row + writeBlock := func(_ uint, db *logstorage.DataBlock) { + columns := db.Columns + if len(columns) == 0 { + return + } + clonedColumnNames := make([]string, len(columns)) + valuesCount := 0 + for i, c := range columns { + clonedColumnNames[i] = strings.Clone(c.Name) + if len(c.Values) > valuesCount { + valuesCount = len(c.Values) + } + } + if valuesCount == 0 { + return + } + for i := 0; i < valuesCount; i++ { + fields := make([]logstorage.Field, 0, len(columns)) + for j := range columns { + fields = append( + fields, + logstorage.Field{ + Name: clonedColumnNames[j], + Value: strings.Clone(columns[j].Values[i]), + }, + ) + } + rowsLock.Lock() + rows = append(rows, &Row{ + Fields: fields, + }) + rowsLock.Unlock() + } + } + + if err = vtstorage.RunQuery(qctx, writeBlock); err != nil { + return nil, err + } + + return rows, nil +} + +// GetServiceGraphTimeRange is an internal function used by service graph background task. +// It calculates the service graph relation within the time range in (parent, child, callCount) format for specific tenant. +func GetServiceGraphTimeRange(ctx context.Context, tenantID logstorage.TenantID, startTime, endTime time.Time, limit uint64) ([][]logstorage.Field, error) { + cp := &CommonParams{ + TenantIDs: []logstorage.TenantID{tenantID}, + } + + // (NOT parent_span_id:"") AND (kind:~"2|5") | fields parent_span_id, resource_attr:service.name | rename parent_span_id as span_id, resource_attr:service.name as child + qStrChildSpans := fmt.Sprintf( + `(NOT %s:"") AND (%s:~"%d|%d") | fields %s, %s | rename %s as %s, %s as %s`, + otelpb.ParentSpanIDField, // parent span id not empty means this span is a child span. + otelpb.KindField, // only server(2) and consumer(5) span could be used as a child. It helps reduce the spans it needs to fetch. + otelpb.SpanKind(2), + otelpb.SpanKind(5), + otelpb.ParentSpanIDField, + otelpb.ResourceAttrServiceName, + otelpb.ParentSpanIDField, + otelpb.SpanIDField, + otelpb.ResourceAttrServiceName, + otelpb.ServiceGraphChildFieldName, + ) + // (NOT span_id:"") AND (kind:~"3|4") | fields span_id, resource_attr:service.name | rename resource_attr:service.name as parent + qStrParentSpans := fmt.Sprintf( + `(NOT %s:"") AND (%s:~"%d|%d") | fields %s, %s | rename %s as %s`, + otelpb.SpanIDField, // Any span could be a parent span, as long as it has a span ID. + otelpb.KindField, // only client(3) and producer(4) span could be used as a parent. It helps reduce the spans it needs to fetch. + otelpb.SpanKind(3), + otelpb.SpanKind(4), + otelpb.SpanIDField, + otelpb.ResourceAttrServiceName, + otelpb.ResourceAttrServiceName, + otelpb.ServiceGraphParentFieldName, + ) + // join by span_id + qStr := fmt.Sprintf( + `%s | join by (%s) (%s) inner | NOT %s:eq_field(%s) | stats by (%s, %s) count() %s`, + qStrChildSpans, + otelpb.SpanIDField, + qStrParentSpans, + otelpb.ServiceGraphParentFieldName, + otelpb.ServiceGraphChildFieldName, + otelpb.ServiceGraphParentFieldName, + otelpb.ServiceGraphChildFieldName, + otelpb.ServiceGraphCallCountFieldName, + ) + + q, err := logstorage.ParseQueryAtTimestamp(qStr, endTime.UnixNano()) + if err != nil { + return nil, fmt.Errorf("cannot parse query [%s]: %s", qStr, err) + } + q.AddTimeFilter(startTime.UnixNano(), endTime.UnixNano()) + q.AddPipeOffsetLimit(0, limit) + + cp.Query = q + qctx := cp.NewQueryContext(ctx) + defer cp.UpdatePerQueryStatsMetrics() + + var rowsLock sync.Mutex + var rows [][]logstorage.Field + writeBlock := func(_ uint, db *logstorage.DataBlock) { + columns := db.Columns + if len(columns) == 0 { + return + } + clonedColumnNames := make([]string, len(columns)) + valuesCount := 0 + for i, c := range columns { + clonedColumnNames[i] = strings.Clone(c.Name) + if len(c.Values) > valuesCount { + valuesCount = len(c.Values) + } + } + if valuesCount == 0 { + return + } + for i := 0; i < valuesCount; i++ { + fields := make([]logstorage.Field, 0, len(columns)) + for j := range clonedColumnNames { + fields = append( + fields, + logstorage.Field{ + Name: clonedColumnNames[j], + Value: strings.Clone(columns[j].Values[i]), + }, + ) + } + rowsLock.Lock() + rows = append(rows, fields) + rowsLock.Unlock() + } + } + + if err = vtstorage.RunQuery(qctx, writeBlock); err != nil { + return nil, fmt.Errorf("cannot execute query [%s]: %s", qStr, err) + } + + return rows, nil +} diff --git a/app/vtstorage/main.go b/app/vtstorage/main.go index 386512f3f..1f51a476e 100644 --- a/app/vtstorage/main.go +++ b/app/vtstorage/main.go @@ -1,6 +1,7 @@ package vtstorage import ( + "context" "encoding/json" "flag" "fmt" @@ -488,6 +489,13 @@ func GetStreamIDs(qctx *logstorage.QueryContext, limit uint64) ([]logstorage.Val return netstorageSelect.GetStreamIDs(qctx, limit) } +func GetTenantIDsByTimeRange(ctx context.Context, startTime, endTime int64) ([]logstorage.TenantID, error) { + if localStorage == nil { + return nil, nil + } + return localStorage.GetTenantIDs(ctx, startTime, endTime) +} + func writeStorageMetrics(w io.Writer, strg *logstorage.Storage) { var ss logstorage.StorageStats strg.UpdateStats(&ss) diff --git a/apptest/client.go b/apptest/client.go index 01ee9761b..cbf1e8279 100644 --- a/apptest/client.go +++ b/apptest/client.go @@ -2,6 +2,7 @@ package apptest import ( "bytes" + "fmt" "io" "net" "net/http" @@ -134,9 +135,19 @@ func (app *ServesMetrics) GetIntMetric(t *testing.T, metricName string) int { func (app *ServesMetrics) GetMetric(t *testing.T, metricName string) float64 { t.Helper() + value, err := app.TryGetMetric(t, metricName) + if err != nil { + t.Fatalf("get metric error: %s, %v", metricName, err) + } + return value +} + +func (app *ServesMetrics) TryGetMetric(t *testing.T, metricName string) (float64, error) { + t.Helper() + metrics, statusCode := app.cli.Get(t, app.metricsURL) if statusCode != http.StatusOK { - t.Fatalf("unexpected status code: got %d, want %d", statusCode, http.StatusOK) + return 0, fmt.Errorf("unexpected status code: got %d, want %d", statusCode, http.StatusOK) } for _, metric := range strings.Split(metrics, "\n") { value, found := strings.CutPrefix(metric, metricName) @@ -144,13 +155,12 @@ func (app *ServesMetrics) GetMetric(t *testing.T, metricName string) float64 { value = strings.Trim(value, " ") res, err := strconv.ParseFloat(value, 64) if err != nil { - t.Fatalf("could not parse metric value %s: %v", metric, err) + return 0, fmt.Errorf("could not parse metric value %s: %v", metric, err) } - return res + return res, nil } } - t.Fatalf("metric not found: %s", metricName) - return 0 + return 0, fmt.Errorf("metric not found: %s", metricName) } // GetMetricsByPrefix retrieves the values of all metrics that start with given diff --git a/apptest/model.go b/apptest/model.go index 9cd59cb07..1a05c9db5 100644 --- a/apptest/model.go +++ b/apptest/model.go @@ -63,7 +63,7 @@ type JaegerQuerier interface { JaegerAPIOperations(t *testing.T, serviceName string, opts QueryOpts) *JaegerAPIOperationsResponse JaegerAPITraces(t *testing.T, params JaegerQueryParam, opts QueryOpts) *JaegerAPITracesResponse JaegerAPITrace(t *testing.T, traceID string, opts QueryOpts) *JaegerAPITraceResponse - JaegerAPIDependencies(t *testing.T, opts QueryOpts) + JaegerAPIDependencies(t *testing.T, params JaegerDependenciesParam, opts QueryOpts) *JaegerAPIDependenciesResponse } // OTLPTracesWriter contains methods for writing OTLP trace data. @@ -259,3 +259,50 @@ func NewJaegerAPITraceResponse(t *testing.T, s string) *JaegerAPITraceResponse { } return res } + +// NewJaegerAPIDependenciesResponse is a test helper function that creates a new +// instance of JaegerAPIDependenciesResponse by unmarshalling a json string. +func NewJaegerAPIDependenciesResponse(t *testing.T, s string) *JaegerAPIDependenciesResponse { + t.Helper() + + res := &JaegerAPIDependenciesResponse{} + if err := json.Unmarshal([]byte(s), res); err != nil { + t.Fatalf("could not unmarshal query response data=\n%s\n: %v", string(s), err) + } + return res +} + +// JaegerDependenciesParam is a helper structure for implementing extra +// helper functions of `query.ServiceGraphQueryParameters`. +type JaegerDependenciesParam struct { + query.ServiceGraphQueryParameters +} + +// asURLValues add non-empty jaeger dependencies params as URL values. +func (jqp *JaegerDependenciesParam) asURLValues() url.Values { + uv := make(url.Values) + addNonEmpty := func(name string, values ...string) { + for _, value := range values { + if len(value) == 0 { + continue + } + uv.Add(name, value) + } + } + + addNonEmpty("endTs", strconv.FormatInt(jqp.EndTs.UnixMilli(), 10)) + addNonEmpty("lookback", strconv.FormatInt(jqp.Lookback.Milliseconds(), 10)) + + return uv +} + +type JaegerAPIDependenciesResponse struct { + Data []DependenciesResponseData `json:"data"` + JaegerResponse +} + +type DependenciesResponseData struct { + Parent string `json:"parent"` + Child string `json:"child"` + CallCount int `json:"callCount"` +} diff --git a/apptest/tests/service_graph_test.go b/apptest/tests/service_graph_test.go new file mode 100644 index 000000000..501dd4bd1 --- /dev/null +++ b/apptest/tests/service_graph_test.go @@ -0,0 +1,314 @@ +package tests + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/VictoriaMetrics/VictoriaTraces/app/vtselect/traces/query" + at "github.com/VictoriaMetrics/VictoriaTraces/apptest" + otelpb "github.com/VictoriaMetrics/VictoriaTraces/lib/protoparser/opentelemetry/pb" +) + +// TestSingleServiceGraphGenerationJaegerQuery test service graph data generation +// and query of `/select/jaeger/api/dependencies` API for vt-single. +func TestSingleServiceGraphGenerationJaegerQuery(t *testing.T) { + os.RemoveAll(t.Name()) + + tc := at.NewTestCase(t) + defer tc.Stop() + + sut := tc.MustStartVtsingle("vtsingle", []string{ + "-storageDataPath=" + tc.Dir() + "/vtsingle", + "-retentionPeriod=100y", + "-servicegraph.enableTask=true", + "-servicegraph.taskInterval=1s", + }) + + testServiceGraphGenerationJaegerQuery(tc, sut) +} + +func testServiceGraphGenerationJaegerQuery(tc *at.TestCase, sut at.VictoriaTracesWriteQuerier) { + t := tc.T() + + // prepareTraceParentAndChildSpanData generate 4 spans: + // 1. parentService span (with span kind=client) calls childService span (with span kind=server) + // 2. childService span (with span kind=server) calls parentService span (with span kind=client) + // + // Since `server` calls `client` is an invalid case, + // this 4 spans should generate only 1 relation edge: parentService->childService. + parentServiceName, childServiceName := prepareTraceParentAndChildSpanData(tc, sut) + + // wait for service graph data to be generated + tc.Assert(&at.AssertOptions{ + Msg: "service graph data not generated", + Got: func() any { + return getServiceGraphRowsInsertedTotal(t, sut) >= 1 + }, + Want: true, + Retries: 10, + Period: time.Second, + }) + sut.ForceFlush(t) + + // verify the service graph relations via /select/jaeger/api/dependencies + tc.Assert(&at.AssertOptions{ + Msg: "unexpected /select/jaeger/api/dependencies response", + Got: func() any { + return sut.JaegerAPIDependencies(t, at.JaegerDependenciesParam{ + ServiceGraphQueryParameters: query.ServiceGraphQueryParameters{ + EndTs: time.Now(), + Lookback: time.Minute, + }, + }, at.QueryOpts{}) + }, + Want: &at.JaegerAPIDependenciesResponse{ + Data: []at.DependenciesResponseData{ + { + Parent: parentServiceName, + Child: childServiceName, + CallCount: 1, + }, + }, + }, + CmpOpts: []cmp.Option{ + cmpopts.IgnoreFields(at.JaegerAPIDependenciesResponse{}, "Errors", "Limit", "Offset", "Total"), + cmpopts.IgnoreFields(at.DependenciesResponseData{}, "CallCount"), + }, + }) +} + +func prepareTraceParentAndChildSpanData(tc *at.TestCase, sut at.VictoriaTracesWriteQuerier) (string, string) { + t := tc.T() + + // important data required by verification. + parentServiceName := "testServiceGraphServiceNameParent" + childServiceName := "testServiceGraphServiceNameChild" + + // prepare test data for ingestion and assertion. + parentSpanID := "987654321" + childSpanID := "9876543210" + + spanName := "testKeyIngestQuerySpan" + traceID := "123456789" + testTagValue := "testValue" + testTag := []*otelpb.KeyValue{ + { + Key: "testTag", + Value: &otelpb.AnyValue{ + StringValue: &testTagValue, + }, + }, + } + spanTime := time.Now() + + parentSpanReq := &otelpb.ExportTraceServiceRequest{ + ResourceSpans: []*otelpb.ResourceSpans{ + { + Resource: otelpb.Resource{ + Attributes: []*otelpb.KeyValue{ + { + Key: "service.name", + Value: &otelpb.AnyValue{ + StringValue: &parentServiceName, + }, + }, + }, + }, + ScopeSpans: []*otelpb.ScopeSpans{ + { + Scope: otelpb.InstrumentationScope{ + Name: "testInstrumentation", + Version: "1.0", + }, + Spans: []*otelpb.Span{ + { + TraceID: traceID, + SpanID: parentSpanID, + TraceState: "trace_state", + ParentSpanID: "", // root span + Flags: 1, + Name: spanName, + Kind: otelpb.SpanKind(3), // parent span must be 3 or 4, 3 means client + StartTimeUnixNano: uint64(spanTime.UnixNano()), + EndTimeUnixNano: uint64(spanTime.UnixNano()), + Attributes: testTag, + Events: []*otelpb.SpanEvent{}, + Links: []*otelpb.SpanLink{}, + Status: otelpb.Status{}, + }, + }, + }, + }, + }, + }, + } + + childSpanReq := &otelpb.ExportTraceServiceRequest{ + ResourceSpans: []*otelpb.ResourceSpans{ + { + Resource: otelpb.Resource{ + Attributes: []*otelpb.KeyValue{ + { + Key: "service.name", + Value: &otelpb.AnyValue{ + StringValue: &childServiceName, + }, + }, + }, + }, + ScopeSpans: []*otelpb.ScopeSpans{ + { + Scope: otelpb.InstrumentationScope{ + Name: "testInstrumentation", + Version: "1.0", + }, + Spans: []*otelpb.Span{ + { + TraceID: traceID, + SpanID: childSpanID, + TraceState: "trace_state", + ParentSpanID: parentSpanID, + Flags: 1, + Name: spanName, + Kind: otelpb.SpanKind(2), // child span must be 2 or 5, 2 means server + StartTimeUnixNano: uint64(spanTime.UnixNano()), + EndTimeUnixNano: uint64(spanTime.UnixNano()), + Attributes: testTag, + Events: []*otelpb.SpanEvent{}, + Links: []*otelpb.SpanLink{}, + Status: otelpb.Status{}, + }, + }, + }, + }, + }, + }, + } + + // ingest data via /insert/opentelemetry/v1/traces + sut.OTLPExportTraces(t, parentSpanReq, at.QueryOpts{}) + sut.OTLPExportTraces(t, childSpanReq, at.QueryOpts{}) + + // case: 2 + // ingest invalid data via /insert/opentelemetry/v1/traces + // the invalid data attempt to generate `child service (calls) parent service` relation. + // but the span kind was set to incorrect (server calls client). + //So it should not generate a service graph edge in the result. + + // prepare test data for ingestion and assertion. + parentSpanID = "0987654321" + childSpanID = "09876543210" + + spanName = "testKeyIngestQuerySpan_invalid" + traceID = "0123456789" + + invalidParentSpanReq := &otelpb.ExportTraceServiceRequest{ + ResourceSpans: []*otelpb.ResourceSpans{ + { + Resource: otelpb.Resource{ + Attributes: []*otelpb.KeyValue{ + { + Key: "service.name", + Value: &otelpb.AnyValue{ + StringValue: &childServiceName, // attempt to generate `child calls parent`, so parent service should be `child`. + }, + }, + }, + }, + ScopeSpans: []*otelpb.ScopeSpans{ + { + Scope: otelpb.InstrumentationScope{ + Name: "testInstrumentation", + Version: "1.0", + }, + Spans: []*otelpb.Span{ + { + TraceID: traceID, + SpanID: parentSpanID, + TraceState: "trace_state", + ParentSpanID: "", // root span + Flags: 1, + Name: spanName, + Kind: otelpb.SpanKind(2), // parent span set to 2 (server), which is invalid + StartTimeUnixNano: uint64(spanTime.UnixNano()), + EndTimeUnixNano: uint64(spanTime.UnixNano()), + Attributes: testTag, + Events: []*otelpb.SpanEvent{}, + Links: []*otelpb.SpanLink{}, + Status: otelpb.Status{}, + }, + }, + }, + }, + }, + }, + } + + invalidChildSpanReq := &otelpb.ExportTraceServiceRequest{ + ResourceSpans: []*otelpb.ResourceSpans{ + { + Resource: otelpb.Resource{ + Attributes: []*otelpb.KeyValue{ + { + Key: "service.name", + Value: &otelpb.AnyValue{ + StringValue: &parentServiceName, // attempt to generate `child calls parent`, so child service should be `parent`. + }, + }, + }, + }, + ScopeSpans: []*otelpb.ScopeSpans{ + { + Scope: otelpb.InstrumentationScope{ + Name: "testInstrumentation", + Version: "1.0", + }, + Spans: []*otelpb.Span{ + { + TraceID: traceID, + SpanID: childSpanID, + TraceState: "trace_state", + ParentSpanID: parentSpanID, + Flags: 1, + Name: spanName, + Kind: otelpb.SpanKind(3), // child span set to 3 (client), which is invalid + StartTimeUnixNano: uint64(spanTime.UnixNano()), + EndTimeUnixNano: uint64(spanTime.UnixNano()), + Attributes: testTag, + Events: []*otelpb.SpanEvent{}, + Links: []*otelpb.SpanLink{}, + Status: otelpb.Status{}, + }, + }, + }, + }, + }, + }, + } + + sut.OTLPExportTraces(t, invalidParentSpanReq, at.QueryOpts{}) + sut.OTLPExportTraces(t, invalidChildSpanReq, at.QueryOpts{}) + return parentServiceName, childServiceName +} + +func getServiceGraphRowsInsertedTotal(t *testing.T, sut at.VictoriaTracesWriteQuerier) int { + t.Helper() + + selector := `vt_rows_ingested_total{type="internalinsert_servicegraph"}` + switch tt := sut.(type) { + case *at.Vtsingle: + // use TryGetMetric instead of TryMetric, to allow retries. + value, err := tt.TryGetMetric(t, selector) + if err != nil { + t.Logf("try get service graph rows failed: %v", err) + } + return int(value) + default: + t.Fatalf("unexpected type: got %T, want *Vtsingle", sut) + } + return 0 +} diff --git a/apptest/vtsingle.go b/apptest/vtsingle.go index 56b291968..546f91100 100644 --- a/apptest/vtsingle.go +++ b/apptest/vtsingle.go @@ -23,10 +23,11 @@ type Vtsingle struct { forceFlushURL string forceMergeURL string - jaegerAPIServicesURL string - jaegerAPIOperationsURL string - jaegerAPITracesURL string - jaegerAPITraceURL string + jaegerAPIServicesURL string + jaegerAPIOperationsURL string + jaegerAPITracesURL string + jaegerAPITraceURL string + jaegerAPIDependenciesURL string otlpTracesURL string } @@ -61,10 +62,11 @@ func StartVtsingle(instance string, flags []string, cli *Client) (*Vtsingle, err forceFlushURL: fmt.Sprintf("http://%s/internal/force_flush", stderrExtracts[1]), forceMergeURL: fmt.Sprintf("http://%s/internal/force_merge", stderrExtracts[1]), - jaegerAPIServicesURL: fmt.Sprintf("http://%s/select/jaeger/api/services", stderrExtracts[1]), - jaegerAPIOperationsURL: fmt.Sprintf("http://%s/select/jaeger/api/services/%%s/operations", stderrExtracts[1]), - jaegerAPITracesURL: fmt.Sprintf("http://%s/select/jaeger/api/traces", stderrExtracts[1]), - jaegerAPITraceURL: fmt.Sprintf("http://%s/select/jaeger/api/traces/%%s", stderrExtracts[1]), + jaegerAPIServicesURL: fmt.Sprintf("http://%s/select/jaeger/api/services", stderrExtracts[1]), + jaegerAPIOperationsURL: fmt.Sprintf("http://%s/select/jaeger/api/services/%%s/operations", stderrExtracts[1]), + jaegerAPITracesURL: fmt.Sprintf("http://%s/select/jaeger/api/traces", stderrExtracts[1]), + jaegerAPITraceURL: fmt.Sprintf("http://%s/select/jaeger/api/traces/%%s", stderrExtracts[1]), + jaegerAPIDependenciesURL: fmt.Sprintf("http://%s/select/jaeger/api/dependencies", stderrExtracts[1]), otlpTracesURL: fmt.Sprintf("http://%s/insert/opentelemetry/v1/traces", stderrExtracts[1]), }, nil @@ -143,7 +145,21 @@ func (app *Vtsingle) JaegerAPITrace(t *testing.T, traceID string, opts QueryOpts // JaegerAPIDependencies is a test helper function that queries for the dependencies. // This method is not implemented in Vtsingle and this test is no-op for now. -func (app *Vtsingle) JaegerAPIDependencies(_ *testing.T, _ QueryOpts) {} +func (app *Vtsingle) JaegerAPIDependencies(t *testing.T, param JaegerDependenciesParam, opts QueryOpts) *JaegerAPIDependenciesResponse { + t.Helper() + + paramsEnc := "?" + values := opts.asURLValues() + if len(values) > 0 { + paramsEnc += values.Encode() + "&" + } + uv := param.asURLValues() + if len(uv) > 0 { + paramsEnc += uv.Encode() + } + res, _ := app.cli.Get(t, app.jaegerAPIDependenciesURL+paramsEnc) + return NewJaegerAPIDependenciesResponse(t, res) +} // OTLPExportTraces is a test helper function that exports OTLP trace data // by sending an HTTP POST request to /insert/opentelemetry/v1/traces diff --git a/docs/victoriatraces/README.md b/docs/victoriatraces/README.md index 5122a6d67..1c33a3fde 100644 --- a/docs/victoriatraces/README.md +++ b/docs/victoriatraces/README.md @@ -521,6 +521,16 @@ It is recommended protecting internal HTTP endpoints from unauthorized access: Whether to disable /select/* HTTP endpoints -select.disableCompression Whether to disable compression for select query responses received from -storageNode nodes. Disabled compression reduces CPU usage at the cost of higher network usage + -servicegraph.enableTask + Whether to enable background task for generating service graph. It should only be enabled on VictoriaTraces single-node or vtstorage. + -servicegraph.taskInterval duration + The background task interval for generating service graph data. It requires setting -servicegraph.enableTask=true. (default 1m0s) + -servicegraph.taskLimit uint + How many service graph relations each task could fetch for each tenant. It requires setting -servicegraph.enableTask=true. (default 1000) + -servicegraph.taskLookbehind duration + The lookbehind window for each time service graph background task run. It requires setting -servicegraph.enableTask=true. (default 1m0s) + -servicegraph.taskTimeout duration + The background task timeout duration for generating service graph data. It requires setting -servicegraph.enableTask=true. (default 30s) -storage.minFreeDiskSpaceBytes size The minimum free disk space at -storageDataPath after which the storage stops accepting new data Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 10000000) diff --git a/docs/victoriatraces/changelog/CHANGELOG.md b/docs/victoriatraces/changelog/CHANGELOG.md index 5111be40f..a33a4ab2a 100644 --- a/docs/victoriatraces/changelog/CHANGELOG.md +++ b/docs/victoriatraces/changelog/CHANGELOG.md @@ -19,6 +19,8 @@ The following `tip` changes can be tested by building VictoriaTraces components * BUGFIX: all components: restore sorting order of summary and quantile metrics exposed by VictoriaTraces components on `/metrics` page. See [metrics#105](https://github.com/VictoriaMetrics/metrics/pull/105) for details. +* FEATURE: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): (experimental) support Jaeger [service dependencies graph API](https://www.jaegertracing.io/docs/2.10/architecture/apis/#service-dependencies-graph). It requires `--servicegraph.enableTask=true` flag to be set on Single-node VictoriaTraces or each vtstorage instance. See [#52](https://github.com/VictoriaMetrics/VictoriaTraces/pull/52) for details. + ## [v0.3.0](https://github.com/VictoriaMetrics/VictoriaTraces/releases/tag/v0.3.0) Released at 2025-09-19 diff --git a/docs/victoriatraces/querying/README.md b/docs/victoriatraces/querying/README.md index b7ffb442a..8fea75707 100644 --- a/docs/victoriatraces/querying/README.md +++ b/docs/victoriatraces/querying/README.md @@ -49,6 +49,7 @@ VictoriaTraces provides the following Jaeger HTTP endpoints: - `/select/jaeger/api/services` for querying all the services - `/select/jaeger/api/services/{service_name}/operations` for querying all the span names of a service. - `/select/jaeger/api/traces/{trace_id}` for querying a trace. +- `/select/jaeger/api/dependencies` for querying the service dependency graph. - `/select/jaeger/api/traces` for querying traces. The `/select/jaeger/api/traces` HTTP endpoint provides the following params: @@ -129,6 +130,30 @@ Here's a response example: {"data":[{"processes":{"p1":{"serviceName":"email","tags":[{"key":"process.command","type":"string","value":"email_server.rb"},{"key":"process.pid","type":"string","value":"1"},{"key":"process.runtime.description","type":"string","value":"ruby 3.4.4 (2025-05-14 revision a38531fd3f) +PRISM [aarch64-linux-musl]"},{"key":"process.runtime.name","type":"string","value":"ruby"},{"key":"process.runtime.version","type":"string","value":"3.4.4"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"ruby"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.8.0"}]},"p10":{"serviceName":"load-generator","tags":[{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"python"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.34.0"}]},"p11":{"serviceName":"product-catalog","tags":[{"key":"host.name","type":"string","value":"3dabfcfe8381"},{"key":"os.description","type":"string","value":"Debian GNU/Linux Debian GNU/Linux 12 (bookworm) (Linux 3dabfcfe8381 6.10.14-linuxkit #1 SMP Tue Apr 15 16:00:54 UTC 2025 aarch64)"},{"key":"os.type","type":"string","value":"linux"},{"key":"process.command_args","type":"string","value":"[\"./product-catalog\"]"},{"key":"process.executable.name","type":"string","value":"product-catalog"},{"key":"process.executable.path","type":"string","value":"/usr/src/app/product-catalog"},{"key":"process.owner","type":"string","value":"nonroot"},{"key":"process.pid","type":"string","value":"1"},{"key":"process.runtime.description","type":"string","value":"go version go1.24.4 linux/arm64"},{"key":"process.runtime.name","type":"string","value":"go"},{"key":"process.runtime.version","type":"string","value":"go1.24.4"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"go"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.36.0"}]},"p12":{"serviceName":"currency","tags":[{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"cpp"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.20.0"}]},"p2":{"serviceName":"quote","tags":[{"key":"container.id","type":"string","value":"759183873eeb1328f16df8ea5b5a10932506af136a6537c6a365131c04f1645c"},{"key":"host.arch","type":"string","value":"aarch64"},{"key":"host.name","type":"string","value":"759183873eeb"},{"key":"os.description","type":"string","value":"6.10.14-linuxkit"},{"key":"os.name","type":"string","value":"Linux"},{"key":"os.type","type":"string","value":"linux"},{"key":"os.version","type":"string","value":"#1 SMP Tue Apr 15 16:00:54 UTC 2025"},{"key":"process.command","type":"string","value":"public/index.php"},{"key":"process.command_args","type":"string","value":"[\"public/index.php\"]"},{"key":"process.executable.path","type":"string","value":"/usr/local/bin/php"},{"key":"process.owner","type":"string","value":"www-data"},{"key":"process.pid","type":"string","value":"1"},{"key":"process.runtime.name","type":"string","value":"cli"},{"key":"process.runtime.version","type":"string","value":"8.3.22"},{"key":"service.instance.id","type":"string","value":"9dc0abaa-c408-483e-9fed-8375a73efb91"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.distro.name","type":"string","value":"opentelemetry-php-instrumentation"},{"key":"telemetry.distro.version","type":"string","value":"1.1.3"},{"key":"telemetry.sdk.language","type":"string","value":"php"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.5.0"}]},"p3":{"serviceName":"frontend","tags":[{"key":"container.id","type":"string","value":"2d395f01353040612a00252cf6e8c32f00ab94ae06f82f143a3ea9c742072674"},{"key":"host.arch","type":"string","value":"arm64"},{"key":"host.name","type":"string","value":"2d395f013530"},{"key":"os.type","type":"string","value":"linux"},{"key":"os.version","type":"string","value":"6.10.14-linuxkit"},{"key":"process.command","type":"string","value":"/app/server.js"},{"key":"process.command_args","type":"string","value":"[\"/usr/local/bin/node\",\"--require\",\"./Instrumentation.js\",\"/app/server.js\"]"},{"key":"process.executable.name","type":"string","value":"node"},{"key":"process.executable.path","type":"string","value":"/usr/local/bin/node"},{"key":"process.owner","type":"string","value":"nextjs"},{"key":"process.pid","type":"string","value":"17"},{"key":"process.runtime.description","type":"string","value":"Node.js"},{"key":"process.runtime.name","type":"string","value":"nodejs"},{"key":"process.runtime.version","type":"string","value":"22.16.0"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"nodejs"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.30.1"}]},"p4":{"serviceName":"payment","tags":[{"key":"container.id","type":"string","value":"18ee03279d38ed0e0eedad037c260df78dfc3323aa662ca14a2d38fcc8bf3762"},{"key":"host.arch","type":"string","value":"arm64"},{"key":"host.name","type":"string","value":"18ee03279d38"},{"key":"os.type","type":"string","value":"linux"},{"key":"os.version","type":"string","value":"6.10.14-linuxkit"},{"key":"process.command","type":"string","value":"/usr/src/app/index.js"},{"key":"process.command_args","type":"string","value":"[\"/usr/local/bin/node\",\"--require\",\"./opentelemetry.js\",\"/usr/src/app/index.js\"]"},{"key":"process.executable.name","type":"string","value":"node"},{"key":"process.executable.path","type":"string","value":"/usr/local/bin/node"},{"key":"process.owner","type":"string","value":"node"},{"key":"process.pid","type":"string","value":"17"},{"key":"process.runtime.description","type":"string","value":"Node.js"},{"key":"process.runtime.name","type":"string","value":"nodejs"},{"key":"process.runtime.version","type":"string","value":"22.16.0"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"nodejs"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.30.1"}]},"p5":{"serviceName":"flagd","tags":[{"key":"host.name","type":"string","value":"1f315d8a0f78"},{"key":"os.description","type":"string","value":"Debian GNU/Linux Debian GNU/Linux 12 (bookworm) (Linux 1f315d8a0f78 6.10.14-linuxkit #1 SMP Tue Apr 15 16:00:54 UTC 2025 aarch64)"},{"key":"os.type","type":"string","value":"linux"},{"key":"process.runtime.version","type":"string","value":"go1.24.1"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"v0.12.3"},{"key":"telemetry.sdk.language","type":"string","value":"go"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.35.0"}]},"p6":{"serviceName":"shipping","tags":[{"key":"os.type","type":"string","value":"linux"},{"key":"process.command_args","type":"string","value":"[\"/app/shipping\"]"},{"key":"process.pid","type":"string","value":"1"},{"key":"process.runtime.description","type":"string","value":"rustc 1.82.0 (f6e511eec 2024-10-15)"},{"key":"process.runtime.name","type":"string","value":"rustc"},{"key":"process.runtime.version","type":"string","value":"1.82.0"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"rust"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"0.30.0"}]},"p7":{"serviceName":"checkout","tags":[{"key":"host.name","type":"string","value":"cbdb5e0808c2"},{"key":"os.description","type":"string","value":"Debian GNU/Linux Debian GNU/Linux 12 (bookworm) (Linux cbdb5e0808c2 6.10.14-linuxkit #1 SMP Tue Apr 15 16:00:54 UTC 2025 aarch64)"},{"key":"os.type","type":"string","value":"linux"},{"key":"process.command_args","type":"string","value":"[\"./checkout\"]"},{"key":"process.executable.name","type":"string","value":"checkout"},{"key":"process.executable.path","type":"string","value":"/usr/src/app/checkout"},{"key":"process.owner","type":"string","value":"nonroot"},{"key":"process.pid","type":"string","value":"1"},{"key":"process.runtime.description","type":"string","value":"go version go1.24.4 linux/arm64"},{"key":"process.runtime.name","type":"string","value":"go"},{"key":"process.runtime.version","type":"string","value":"go1.24.4"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"go"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.36.0"}]},"p8":{"serviceName":"frontend-proxy","tags":[{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"}]},"p9":{"serviceName":"cart","tags":[{"key":"container.id","type":"string","value":"5603ff989877ecf311403b6ea81fda10734846a0cbdad3a09c39fb068e4a07fc"},{"key":"host.name","type":"string","value":"5603ff989877"},{"key":"service.namespace","type":"string","value":"opentelemetry-demo"},{"key":"service.version","type":"string","value":"2.0.2"},{"key":"telemetry.sdk.language","type":"string","value":"dotnet"},{"key":"telemetry.sdk.name","type":"string","value":"opentelemetry"},{"key":"telemetry.sdk.version","type":"string","value":"1.11.2"}]}},"spans":[{"duration":4935,"logs":[],"operationName":"send_email","processID":"p1","references":[{"refType":"CHILD_OF","spanID":"739cd04d718779ae","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"032bf7007e123e8d","startTime":1750044449769690,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"email"},{"key":"error","type":"string","value":"unset"},{"key":"app.email.recipient","type":"string","value":"reed@example.com"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":3339,"logs":[{"timestamp":1750044449717803,"fields":[{"key":"event","type":"string","value":"Received get quote request, processing it"}]},{"timestamp":1750044449718100,"fields":[{"key":"event","type":"string","value":"Quote processed, response sent back"},{"key":"app.quote.cost.total","type":"string","value":"227.5"}]}],"operationName":"{closure}","processID":"p2","references":[{"refType":"CHILD_OF","spanID":"aaf29afb62662d95","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"ea80042fbe6e5887","startTime":1750044449717692,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"io.opentelemetry.contrib.php.slim"},{"key":"code.file.path","type":"string","value":"/var/www/vendor/php-di/slim-bridge/src/ControllerInvoker.php"},{"key":"code.function.name","type":"string","value":"DI\\Bridge\\Slim\\ControllerInvoker::__invoke"},{"key":"code.line.number","type":"string","value":"29"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":6544,"logs":[],"operationName":"POST /getquote","processID":"p2","references":[{"refType":"CHILD_OF","spanID":"09b03b9b5481c29c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"aaf29afb62662d95","startTime":1750044449717102,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"io.opentelemetry.contrib.php.slim"},{"key":"code.file.path","type":"string","value":"/var/www/vendor/slim/slim/Slim/App.php"},{"key":"code.function.name","type":"string","value":"Slim\\App::handle"},{"key":"code.line.number","type":"string","value":"207"},{"key":"http.request.body.size","type":"string","value":"19"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.body.size","type":"string","value":"-"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"http.route","type":"string","value":"/getquote"},{"key":"network.protocol.version","type":"string","value":"1.1"},{"key":"server.address","type":"string","value":"quote"},{"key":"server.port","type":"string","value":"8090"},{"key":"url.full","type":"string","value":"http://quote:8090/getquote"},{"key":"url.path","type":"string","value":"/getquote"},{"key":"url.scheme","type":"string","value":"http"},{"key":"user_agent.original","type":"string","value":"-"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":77220,"logs":[],"operationName":"executing api route (pages) /api/checkout","processID":"p3","references":[{"refType":"CHILD_OF","spanID":"01468af9419620f5","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"6b73da57ebca1b82","startTime":1750044449702000,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"next.js"},{"key":"otel.scope.version","type":"string","value":"0.0.1"},{"key":"http.status_code","type":"string","value":"200"},{"key":"next.span_name","type":"string","value":"executing api route (pages) /api/checkout"},{"key":"next.span_type","type":"string","value":"Node.runHandler"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":78153,"logs":[],"operationName":"POST","processID":"p3","references":[{"refType":"CHILD_OF","spanID":"df1b3d5c8e0ab6be","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"47c48aa63a0c5a3d","startTime":1750044449701000,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"@opentelemetry/instrumentation-http"},{"key":"otel.scope.version","type":"string","value":"0.57.1"},{"key":"http.flavor","type":"string","value":"1.1"},{"key":"http.host","type":"string","value":"frontend-proxy:8080"},{"key":"http.method","type":"string","value":"POST"},{"key":"http.scheme","type":"string","value":"http"},{"key":"http.status_code","type":"string","value":"200"},{"key":"http.user_agent","type":"string","value":"python-requests/2.32.4"},{"key":"net.host.name","type":"string","value":"frontend-proxy"},{"key":"net.peer.ip","type":"string","value":"172.18.0.26"},{"key":"net.transport","type":"string","value":"ip_tcp"},{"key":"error","type":"string","value":"unset"},{"key":"http.request_content_length_uncompressed","type":"string","value":"388"},{"key":"http.status_text","type":"string","value":"OK"},{"key":"http.target","type":"string","value":"/api/checkout"},{"key":"http.url","type":"string","value":"http://frontend-proxy:8080/api/checkout"},{"key":"net.host.ip","type":"string","value":"172.18.0.24"},{"key":"net.host.port","type":"string","value":"8080"},{"key":"net.peer.port","type":"string","value":"35632"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":1988,"logs":[],"operationName":"charge","processID":"p4","references":[{"refType":"CHILD_OF","spanID":"df89f1712cb9fdec","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"f30e92001c694787","startTime":1750044449743000,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"payment"},{"key":"app.payment.card_type","type":"string","value":"visa"},{"key":"app.payment.card_valid","type":"string","value":"true"},{"key":"app.payment.charged","type":"string","value":"false"},{"key":"error","type":"string","value":"unset"},{"key":"app.loyalty.level","type":"string","value":"silver"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":6,"logs":[],"operationName":"resolveBoolean","processID":"p5","references":[{"refType":"CHILD_OF","spanID":"3af2ca071042ef47","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"ab8c870e76bbe57f","startTime":1750044449753032,"tags":[{"key":"error","type":"string","value":"unset"},{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"jsonEvaluator"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":70,"logs":[],"operationName":"resolveBoolean","processID":"p5","references":[{"refType":"CHILD_OF","spanID":"9d054ff4aeb2b518","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"3af2ca071042ef47","startTime":1750044449753027,"tags":[{"key":"error","type":"string","value":"unset"},{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"flagd.evaluation.v1"},{"key":"feature_flag.key","type":"string","value":"cartFailure"},{"key":"feature_flag.provider_name","type":"string","value":"flagd"},{"key":"feature_flag.variant","type":"string","value":"off"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":19817,"logs":[{"timestamp":1750044449735392,"fields":[{"key":"event","type":"string","value":"Received Quote"},{"key":"app.shipping.cost.total","type":"string","value":"227.50"}]}],"operationName":"/get-quote","processID":"p6","references":[{"refType":"CHILD_OF","spanID":"7b92ebafc9a2a0f1","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"599cbbf8e81ddaca","startTime":1750044449715635,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"opentelemetry-instrumentation-actix-web"},{"key":"otel.scope.version","type":"string","value":"0.22.0"},{"key":"client.address","type":"string","value":"172.18.0.23"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"http.route","type":"string","value":"/get-quote"},{"key":"network.protocol.version","type":"string","value":"1.1"},{"key":"server.address","type":"string","value":"shipping"},{"key":"server.port","type":"string","value":"50050"},{"key":"url.path","type":"string","value":"/get-quote"},{"key":"url.scheme","type":"string","value":"http"},{"key":"user_agent.original","type":"string","value":"Go-http-client/1.1"},{"key":"error","type":"string","value":"unset"},{"key":"app.shipping.cost.total","type":"string","value":"227.50"},{"key":"messaging.message.body.size","type":"string","value":"182"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":283,"logs":[],"operationName":"sinatra.render_template","processID":"p1","references":[{"refType":"CHILD_OF","spanID":"1fd5f529c2dd316b","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"bc5f262c2f7d9bb5","startTime":1750044449770317,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry::Instrumentation::Sinatra"},{"key":"otel.scope.version","type":"string","value":"0.25.0"},{"key":"error","type":"string","value":"unset"},{"key":"sinatra.template_name","type":"string","value":"layout"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":961,"logs":[],"operationName":"sinatra.render_template","processID":"p1","references":[{"refType":"CHILD_OF","spanID":"032bf7007e123e8d","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"1fd5f529c2dd316b","startTime":1750044449769761,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry::Instrumentation::Sinatra"},{"key":"otel.scope.version","type":"string","value":"0.25.0"},{"key":"error","type":"string","value":"unset"},{"key":"sinatra.template_name","type":"string","value":"confirmation"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":6755,"logs":[],"operationName":"oteldemo.PaymentService/Charge","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"7683762fa74ffd1c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"530667cc212dd6ed","startTime":1750044449739280,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"Charge"},{"key":"rpc.service","type":"string","value":"oteldemo.PaymentService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.14"},{"key":"server.port","type":"string","value":"50051"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":1831,"logs":[],"operationName":"oteldemo.CartService/GetCart","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"96f2298052cc3fda","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"111cb151fdd9a915","startTime":1750044449708652,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"GetCart"},{"key":"rpc.service","type":"string","value":"oteldemo.CartService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.10"},{"key":"server.port","type":"string","value":"7070"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":46,"logs":[],"operationName":"/ship-order","processID":"p6","references":[{"refType":"CHILD_OF","spanID":"92345ad5d7cb4190","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"d1253691f90f5b95","startTime":1750044449746781,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"opentelemetry-instrumentation-actix-web"},{"key":"otel.scope.version","type":"string","value":"0.22.0"},{"key":"client.address","type":"string","value":"172.18.0.23"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"http.route","type":"string","value":"/ship-order"},{"key":"network.protocol.version","type":"string","value":"1.1"},{"key":"server.address","type":"string","value":"shipping"},{"key":"server.port","type":"string","value":"50050"},{"key":"url.path","type":"string","value":"/ship-order"},{"key":"url.scheme","type":"string","value":"http"},{"key":"user_agent.original","type":"string","value":"Go-http-client/1.1"},{"key":"error","type":"string","value":"unset"},{"key":"messaging.message.body.size","type":"string","value":"182"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":128,"logs":[{"timestamp":1750044449717887,"fields":[{"key":"event","type":"string","value":"Calculating quote"}]},{"timestamp":1750044449717919,"fields":[{"key":"event","type":"string","value":"Quote calculated, returning its value"}]}],"operationName":"calculate-quote","processID":"p2","references":[{"refType":"CHILD_OF","spanID":"ea80042fbe6e5887","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"0b119b964828c67b","startTime":1750044449717886,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"manual-instrumentation"},{"key":"error","type":"string","value":"unset"},{"key":"app.quote.cost.total","type":"string","value":"227.5"},{"key":"app.quote.items.count","type":"string","value":"5"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":78545,"logs":[],"operationName":"router frontend egress","processID":"p8","references":[{"refType":"CHILD_OF","spanID":"d66da216bedd159f","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"df1b3d5c8e0ab6be","startTime":1750044449701376,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"component","type":"string","value":"proxy"},{"key":"http.protocol","type":"string","value":"HTTP/1.1"},{"key":"peer.address","type":"string","value":"172.18.0.24:8080"},{"key":"upstream_address","type":"string","value":"172.18.0.24:8080"},{"key":"upstream_cluster","type":"string","value":"frontend"},{"key":"upstream_cluster.name","type":"string","value":"frontend"},{"key":"error","type":"string","value":"unset"},{"key":"http.status_code","type":"string","value":"200"},{"key":"response_flags","type":"string","value":"-"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":915,"logs":[{"timestamp":1750044449709335,"fields":[{"key":"event","type":"string","value":"Fetch cart"}]}],"operationName":"POST /oteldemo.CartService/GetCart","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"111cb151fdd9a915","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"fefa4832f9254043","startTime":1750044449709238,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"Microsoft.AspNetCore"},{"key":"grpc.method","type":"string","value":"/oteldemo.CartService/GetCart"},{"key":"grpc.status_code","type":"string","value":"0"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"http.route","type":"string","value":"/oteldemo.CartService/GetCart"},{"key":"network.protocol.version","type":"string","value":"2"},{"key":"server.address","type":"string","value":"cart"},{"key":"server.port","type":"string","value":"7070"},{"key":"url.path","type":"string","value":"/oteldemo.CartService/GetCart"},{"key":"url.scheme","type":"string","value":"http"},{"key":"error","type":"string","value":"unset"},{"key":"app.cart.items.count","type":"string","value":"5"},{"key":"app.user.id","type":"string","value":"d526648e-4a61-11f0-8b6b-b20e5443dfb5"},{"key":"user_agent.original","type":"string","value":"grpc-go/1.72.2"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":710,"logs":[],"operationName":"oteldemo.ProductCatalogService/GetProduct","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"96f2298052cc3fda","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"7e5e7c2f1ea9cb0b","startTime":1750044449710565,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"GetProduct"},{"key":"rpc.service","type":"string","value":"oteldemo.ProductCatalogService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.19"},{"key":"server.port","type":"string","value":"3550"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":69871,"logs":[{"timestamp":1750044449737830,"fields":[{"key":"event","type":"string","value":"prepared"}]},{"timestamp":1750044449739261,"fields":[{"key":"feature_flag.key","type":"string","value":"paymentUnreachable"},{"key":"feature_flag.provider_name","type":"string","value":"flagd"},{"key":"feature_flag.variant","type":"string","value":"off"},{"key":"event","type":"string","value":"feature_flag"}]},{"timestamp":1750044449746517,"fields":[{"key":"event","type":"string","value":"charged"},{"key":"app.payment.transaction.id","type":"string","value":"bbf912fe-0a55-4704-8eb9-02d43f60297d"}]},{"timestamp":1750044449746988,"fields":[{"key":"event","type":"string","value":"shipped"},{"key":"app.shipping.tracking.id","type":"string","value":"4668b5f9-17e2-4311-8b20-c7cf3b08ab39"}]},{"timestamp":1750044449776318,"fields":[{"key":"feature_flag.key","type":"string","value":"kafkaQueueProblems"},{"key":"feature_flag.provider_name","type":"string","value":"flagd"},{"key":"feature_flag.variant","type":"string","value":"off"},{"key":"event","type":"string","value":"feature_flag"}]}],"operationName":"oteldemo.CheckoutService/PlaceOrder","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"b1cf4a62984b9984","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"7683762fa74ffd1c","startTime":1750044449706551,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"app.order.items.count","type":"string","value":"1"},{"key":"app.user.currency","type":"string","value":"USD"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"PlaceOrder"},{"key":"rpc.service","type":"string","value":"oteldemo.CheckoutService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.24"},{"key":"server.port","type":"string","value":"38682"},{"key":"error","type":"string","value":"unset"},{"key":"app.order.amount","type":"string","value":"1102"},{"key":"app.order.id","type":"string","value":"d52a1b43-4a61-11f0-9e2b-96226e8767f9"},{"key":"app.shipping.amount","type":"string","value":"227"},{"key":"app.shipping.tracking.id","type":"string","value":"4668b5f9-17e2-4311-8b20-c7cf3b08ab39"},{"key":"app.user.id","type":"string","value":"d526648e-4a61-11f0-8b6b-b20e5443dfb5"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":8349,"logs":[],"operationName":"POST /send_order_confirmation","processID":"p1","references":[{"refType":"CHILD_OF","spanID":"d96adf1246ad7d75","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"739cd04d718779ae","startTime":1750044449766969,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry::Instrumentation::Rack"},{"key":"otel.scope.version","type":"string","value":"0.26.0"},{"key":"http.host","type":"string","value":"email:6060"},{"key":"http.method","type":"string","value":"POST"},{"key":"http.route","type":"string","value":"/send_order_confirmation"},{"key":"http.scheme","type":"string","value":"http"},{"key":"http.status_code","type":"string","value":"200"},{"key":"http.target","type":"string","value":"/send_order_confirmation"},{"key":"http.user_agent","type":"string","value":"Go-http-client/1.1"},{"key":"error","type":"string","value":"unset"},{"key":"app.order.id","type":"string","value":"d52a1b43-4a61-11f0-9e2b-96226e8767f9"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":74743,"logs":[],"operationName":"grpc.oteldemo.CheckoutService/PlaceOrder","processID":"p3","references":[{"refType":"CHILD_OF","spanID":"6b73da57ebca1b82","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"b1cf4a62984b9984","startTime":1750044449702000,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"@opentelemetry/instrumentation-grpc"},{"key":"otel.scope.version","type":"string","value":"0.57.1"},{"key":"net.peer.name","type":"string","value":"checkout"},{"key":"net.peer.port","type":"string","value":"5050"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"PlaceOrder"},{"key":"rpc.service","type":"string","value":"oteldemo.CheckoutService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":12631,"logs":[],"operationName":"oteldemo.CartService/EmptyCart","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"7683762fa74ffd1c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"4e08d386db6de0e6","startTime":1750044449747019,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"EmptyCart"},{"key":"rpc.service","type":"string","value":"oteldemo.CartService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.10"},{"key":"server.port","type":"string","value":"7070"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":11927,"logs":[{"timestamp":1750044449747830,"fields":[{"key":"event","type":"string","value":"Empty cart"}]},{"timestamp":1750044449755100,"fields":[{"key":"feature_flag.key","type":"string","value":"cartFailure"},{"key":"feature_flag.provider_name","type":"string","value":"flagd Provider"},{"key":"feature_flag.variant","type":"string","value":"off"},{"key":"event","type":"string","value":"feature_flag"}]}],"operationName":"POST /oteldemo.CartService/EmptyCart","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"4e08d386db6de0e6","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"d8802687844ff0da","startTime":1750044449747360,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"Microsoft.AspNetCore"},{"key":"app.user.id","type":"string","value":"d526648e-4a61-11f0-8b6b-b20e5443dfb5"},{"key":"feature_flag.key","type":"string","value":"cartFailure"},{"key":"feature_flag.provider_name","type":"string","value":"flagd Provider"},{"key":"feature_flag.variant","type":"string","value":"off"},{"key":"grpc.method","type":"string","value":"/oteldemo.CartService/EmptyCart"},{"key":"grpc.status_code","type":"string","value":"0"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"http.route","type":"string","value":"/oteldemo.CartService/EmptyCart"},{"key":"network.protocol.version","type":"string","value":"2"},{"key":"server.address","type":"string","value":"cart"},{"key":"server.port","type":"string","value":"7070"},{"key":"url.path","type":"string","value":"/oteldemo.CartService/EmptyCart"},{"key":"url.scheme","type":"string","value":"http"},{"key":"user_agent.original","type":"string","value":"grpc-go/1.72.2"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":1733,"logs":[],"operationName":"grpc.oteldemo.ProductCatalogService/GetProduct","processID":"p3","references":[{"refType":"CHILD_OF","spanID":"6b73da57ebca1b82","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"394722a3d65e5bee","startTime":1750044449777000,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"@opentelemetry/instrumentation-grpc"},{"key":"otel.scope.version","type":"string","value":"0.57.1"},{"key":"net.peer.name","type":"string","value":"product-catalog"},{"key":"net.peer.port","type":"string","value":"3550"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"GetProduct"},{"key":"rpc.service","type":"string","value":"oteldemo.ProductCatalogService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":30309,"logs":[],"operationName":"prepareOrderItemsAndShippingQuoteFromCart","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"7683762fa74ffd1c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"96f2298052cc3fda","startTime":1750044449707511,"tags":[{"key":"span.kind","type":"string","value":"internal"},{"key":"otel.scope.name","type":"string","value":"checkout"},{"key":"app.order.items.count","type":"string","value":"1"},{"key":"error","type":"string","value":"unset"},{"key":"app.cart.items.count","type":"string","value":"5"},{"key":"app.shipping.amount","type":"string","value":"227"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":805,"logs":[],"operationName":"orders publish","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"7683762fa74ffd1c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"842ad77105e18d23","startTime":1750044449775517,"tags":[{"key":"span.kind","type":"string","value":"producer"},{"key":"otel.scope.name","type":"string","value":"checkout"},{"key":"messaging.destination.name","type":"string","value":"orders"},{"key":"messaging.kafka.destination.partition","type":"string","value":"0"},{"key":"messaging.kafka.message.offset","type":"string","value":"0"},{"key":"messaging.kafka.producer.success","type":"string","value":"true"},{"key":"messaging.operation","type":"string","value":"publish"},{"key":"messaging.system","type":"string","value":"kafka"},{"key":"network.transport","type":"string","value":"tcp"},{"key":"peer.service","type":"string","value":"kafka"},{"key":"error","type":"string","value":"unset"},{"key":"messaging.kafka.producer.duration_ms","type":"string","value":"0"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":352,"logs":[{"timestamp":1750044449709386,"fields":[{"key":"event","type":"string","value":"Enqueued"}]},{"timestamp":1750044449709400,"fields":[{"key":"event","type":"string","value":"Sent"}]},{"timestamp":1750044449709718,"fields":[{"key":"event","type":"string","value":"ResponseReceived"}]}],"operationName":"HGET","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"fefa4832f9254043","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"1c6fa81981e4960c","startTime":1750044449709366,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry.Instrumentation.StackExchangeRedis"},{"key":"otel.scope.version","type":"string","value":"1.11.0-beta.2"},{"key":"db.redis.database_index","type":"string","value":"0"},{"key":"db.redis.flags","type":"string","value":"None"},{"key":"db.system","type":"string","value":"redis"},{"key":"server.address","type":"string","value":"valkey-cart"},{"key":"server.port","type":"string","value":"6379"},{"key":"error","type":"string","value":"unset"},{"key":"db.statement","type":"string","value":"HGET d526648e-4a61-11f0-8b6b-b20e5443dfb5"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":22024,"logs":[],"operationName":"HTTP POST","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"96f2298052cc3fda","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"7b92ebafc9a2a0f1","startTime":1750044449713664,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"network.protocol.version","type":"string","value":"1.1"},{"key":"error","type":"string","value":"unset"},{"key":"server.address","type":"string","value":"shipping"},{"key":"server.port","type":"string","value":"50050"},{"key":"url.full","type":"string","value":"http://shipping:50050/get-quote"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":391,"logs":[],"operationName":"HTTP POST","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"7683762fa74ffd1c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"92345ad5d7cb4190","startTime":1750044449746559,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"network.protocol.version","type":"string","value":"1.1"},{"key":"error","type":"string","value":"unset"},{"key":"server.address","type":"string","value":"shipping"},{"key":"server.port","type":"string","value":"50050"},{"key":"url.full","type":"string","value":"http://shipping:50050/ship-order"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":4711,"logs":[],"operationName":"POST","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"64e503f233846241","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"31d9931c1b054f86","startTime":1750044449749545,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"System.Net.Http"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"network.protocol.version","type":"string","value":"2"},{"key":"server.address","type":"string","value":"flagd"},{"key":"server.port","type":"string","value":"8013"},{"key":"url.full","type":"string","value":"http://flagd:8013/flagd.evaluation.v1.Service/ResolveBoolean"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":15663,"logs":[],"operationName":"HTTP POST","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"7683762fa74ffd1c","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"d96adf1246ad7d75","startTime":1750044449759771,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"network.protocol.version","type":"string","value":"1.1"},{"key":"error","type":"string","value":"unset"},{"key":"server.address","type":"string","value":"email"},{"key":"server.port","type":"string","value":"6060"},{"key":"url.full","type":"string","value":"http://email:6060/send_order_confirmation"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":3076,"logs":[],"operationName":"grpc.oteldemo.PaymentService/Charge","processID":"p4","references":[{"refType":"CHILD_OF","spanID":"530667cc212dd6ed","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"df89f1712cb9fdec","startTime":1750044449742000,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"@opentelemetry/instrumentation-grpc"},{"key":"otel.scope.version","type":"string","value":"0.57.1"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"Charge"},{"key":"rpc.service","type":"string","value":"oteldemo.PaymentService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"unset"},{"key":"app.payment.amount","type":"string","value":"1102.50"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":79737,"logs":[],"operationName":"POST","processID":"p10","references":[],"spanID":"10d27d153c44c541","startTime":1750044449700847,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"opentelemetry.instrumentation.requests"},{"key":"otel.scope.version","type":"string","value":"0.55b0"},{"key":"http.method","type":"string","value":"POST"},{"key":"http.status_code","type":"string","value":"200"},{"key":"http.url","type":"string","value":"http://frontend-proxy:8080/api/checkout"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":421,"logs":[{"timestamp":1750044449755249,"fields":[{"key":"event","type":"string","value":"Enqueued"}]},{"timestamp":1750044449755262,"fields":[{"key":"event","type":"string","value":"Sent"}]},{"timestamp":1750044449755655,"fields":[{"key":"event","type":"string","value":"ResponseReceived"}]}],"operationName":"HMSET","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"d8802687844ff0da","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"5f78a21a81d1a9a3","startTime":1750044449755233,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry.Instrumentation.StackExchangeRedis"},{"key":"otel.scope.version","type":"string","value":"1.11.0-beta.2"},{"key":"db.redis.database_index","type":"string","value":"0"},{"key":"db.redis.flags","type":"string","value":"DemandMaster"},{"key":"db.system","type":"string","value":"redis"},{"key":"server.address","type":"string","value":"valkey-cart"},{"key":"server.port","type":"string","value":"6379"},{"key":"error","type":"string","value":"unset"},{"key":"db.statement","type":"string","value":"HMSET d526648e-4a61-11f0-8b6b-b20e5443dfb5"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":5855,"logs":[],"operationName":"flagd.evaluation.v1.Service/ResolveBoolean","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"d8802687844ff0da","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"64e503f233846241","startTime":1750044449749012,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry.Instrumentation.GrpcNetClient"},{"key":"otel.scope.version","type":"string","value":"1.11.0-beta.2"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"ResolveBoolean"},{"key":"rpc.service","type":"string","value":"flagd.evaluation.v1.Service"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"flagd"},{"key":"server.port","type":"string","value":"8013"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":136,"logs":[{"timestamp":1750044449752991,"fields":[{"key":"message.id","type":"string","value":"1"},{"key":"message.type","type":"string","value":"RECEIVED"},{"key":"event","type":"string","value":"message"},{"key":"message.uncompressed_size","type":"string","value":"15"}]},{"timestamp":1750044449753111,"fields":[{"key":"message.id","type":"string","value":"1"},{"key":"message.type","type":"string","value":"SENT"},{"key":"message.uncompressed_size","type":"string","value":"15"},{"key":"event","type":"string","value":"message"}]}],"operationName":"flagd.evaluation.v1.Service/ResolveBoolean","processID":"p5","references":[{"refType":"CHILD_OF","spanID":"31d9931c1b054f86","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"9d054ff4aeb2b518","startTime":1750044449752984,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"connectrpc.com/otelconnect"},{"key":"otel.scope.version","type":"string","value":"semver:0.6.0-dev"},{"key":"rpc.method","type":"string","value":"ResolveBoolean"},{"key":"rpc.service","type":"string","value":"flagd.evaluation.v1.Service"},{"key":"error","type":"string","value":"unset"},{"key":"net.peer.name","type":"string","value":"172.18.0.10"},{"key":"net.peer.port","type":"string","value":"46838"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.system","type":"string","value":"grpc"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":877,"logs":[{"timestamp":1750044449755696,"fields":[{"key":"event","type":"string","value":"Enqueued"}]},{"timestamp":1750044449755708,"fields":[{"key":"event","type":"string","value":"Sent"}]},{"timestamp":1750044449756563,"fields":[{"key":"event","type":"string","value":"ResponseReceived"}]}],"operationName":"EXPIRE","processID":"p9","references":[{"refType":"CHILD_OF","spanID":"d8802687844ff0da","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"4a42b7a5fa81bdfb","startTime":1750044449755686,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"OpenTelemetry.Instrumentation.StackExchangeRedis"},{"key":"otel.scope.version","type":"string","value":"1.11.0-beta.2"},{"key":"db.redis.database_index","type":"string","value":"0"},{"key":"db.redis.flags","type":"string","value":"DemandMaster"},{"key":"db.system","type":"string","value":"redis"},{"key":"server.address","type":"string","value":"valkey-cart"},{"key":"server.port","type":"string","value":"6379"},{"key":"error","type":"string","value":"unset"},{"key":"db.statement","type":"string","value":"EXPIRE d526648e-4a61-11f0-8b6b-b20e5443dfb5"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":2157,"logs":[],"operationName":"oteldemo.CurrencyService/Convert","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"96f2298052cc3fda","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"34a9d7aa3afe1688","startTime":1750044449711310,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"Convert"},{"key":"rpc.service","type":"string","value":"oteldemo.CurrencyService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.18"},{"key":"server.port","type":"string","value":"7001"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":2021,"logs":[],"operationName":"oteldemo.CurrencyService/Convert","processID":"p7","references":[{"refType":"CHILD_OF","spanID":"96f2298052cc3fda","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"11295d69d0e661dd","startTime":1750044449735781,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"Convert"},{"key":"rpc.service","type":"string","value":"oteldemo.CurrencyService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"server.address","type":"string","value":"172.18.0.18"},{"key":"server.port","type":"string","value":"7001"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":77796,"logs":[],"operationName":"POST /api/checkout","processID":"p3","references":[{"refType":"CHILD_OF","spanID":"47c48aa63a0c5a3d","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"01468af9419620f5","startTime":1750044449701000,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"next.js"},{"key":"otel.scope.version","type":"string","value":"0.0.1"},{"key":"http.method","type":"string","value":"POST"},{"key":"http.status_code","type":"string","value":"200"},{"key":"http.target","type":"string","value":"/api/checkout"},{"key":"next.rsc","type":"string","value":"false"},{"key":"next.span_name","type":"string","value":"POST /api/checkout"},{"key":"next.span_type","type":"string","value":"BaseServer.handleRequest"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":19397,"logs":[],"operationName":"POST quote","processID":"p6","references":[{"refType":"CHILD_OF","spanID":"599cbbf8e81ddaca","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"09b03b9b5481c29c","startTime":1750044449715774,"tags":[{"key":"span.kind","type":"string","value":"client"},{"key":"otel.scope.name","type":"string","value":"opentelemetry-instrumentation-actix-web"},{"key":"otel.scope.version","type":"string","value":"0.22.0"},{"key":"http.request.method","type":"string","value":"POST"},{"key":"http.response.status_code","type":"string","value":"200"},{"key":"server.address","type":"string","value":"quote"},{"key":"server.port","type":"string","value":"8090"},{"key":"url.full","type":"string","value":"http://quote:8090/getquote"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":75,"logs":[{"timestamp":1750044449711020,"fields":[{"key":"event","type":"string","value":"Product Found"}]}],"operationName":"oteldemo.ProductCatalogService/GetProduct","processID":"p11","references":[{"refType":"CHILD_OF","spanID":"7e5e7c2f1ea9cb0b","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"5b997902f830009b","startTime":1750044449710969,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"GetProduct"},{"key":"rpc.service","type":"string","value":"oteldemo.ProductCatalogService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"unset"},{"key":"app.product.id","type":"string","value":"0PUK6V6EV0"},{"key":"app.product.name","type":"string","value":"Solar System Color Imager"},{"key":"server.address","type":"string","value":"172.18.0.23"},{"key":"server.port","type":"string","value":"56058"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":78,"logs":[{"timestamp":1750044449778775,"fields":[{"key":"event","type":"string","value":"Product Found"}]}],"operationName":"oteldemo.ProductCatalogService/GetProduct","processID":"p11","references":[{"refType":"CHILD_OF","spanID":"394722a3d65e5bee","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"212f00429ff724f5","startTime":1750044449778734,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"},{"key":"otel.scope.version","type":"string","value":"0.61.0"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"GetProduct"},{"key":"rpc.service","type":"string","value":"oteldemo.ProductCatalogService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"unset"},{"key":"app.product.id","type":"string","value":"0PUK6V6EV0"},{"key":"app.product.name","type":"string","value":"Solar System Color Imager"},{"key":"server.address","type":"string","value":"172.18.0.24"},{"key":"server.port","type":"string","value":"47538"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":597,"logs":[{"timestamp":1750044449711719,"fields":[{"key":"event","type":"string","value":"Processing currency conversion request"}]},{"timestamp":1750044449711741,"fields":[{"key":"event","type":"string","value":"Conversion successful, response sent back"}]}],"operationName":"Currency/Convert","processID":"p12","references":[{"refType":"CHILD_OF","spanID":"34a9d7aa3afe1688","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"42e4324fcb045b99","startTime":1750044449711715,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"currency"},{"key":"app.currency.conversion.from","type":"string","value":"USD"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"Convert"},{"key":"rpc.service","type":"string","value":"oteldemo.CurrencyService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"false"},{"key":"app.currency.conversion.to","type":"string","value":"USD"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":655,"logs":[{"timestamp":1750044449736390,"fields":[{"key":"event","type":"string","value":"Processing currency conversion request"}]},{"timestamp":1750044449736414,"fields":[{"key":"event","type":"string","value":"Conversion successful, response sent back"}]}],"operationName":"Currency/Convert","processID":"p12","references":[{"refType":"CHILD_OF","spanID":"11295d69d0e661dd","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"adb556f3c99b633d","startTime":1750044449736386,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"otel.scope.name","type":"string","value":"currency"},{"key":"app.currency.conversion.from","type":"string","value":"USD"},{"key":"rpc.grpc.status_code","type":"string","value":"0"},{"key":"rpc.method","type":"string","value":"Convert"},{"key":"rpc.service","type":"string","value":"oteldemo.CurrencyService"},{"key":"rpc.system","type":"string","value":"grpc"},{"key":"error","type":"string","value":"false"},{"key":"app.currency.conversion.to","type":"string","value":"USD"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null},{"duration":78648,"logs":[],"operationName":"ingress","processID":"p8","references":[{"refType":"CHILD_OF","spanID":"10d27d153c44c541","traceID":"9e06226196051d9c3c10dfab343791ad"}],"spanID":"d66da216bedd159f","startTime":1750044449701298,"tags":[{"key":"span.kind","type":"string","value":"server"},{"key":"component","type":"string","value":"proxy"},{"key":"downstream_cluster","type":"string","value":"-"},{"key":"http.protocol","type":"string","value":"HTTP/1.1"},{"key":"node_id","type":"string","value":"-"},{"key":"peer.address","type":"string","value":"172.18.0.25"},{"key":"zone","type":"string","value":"-"},{"key":"guid:x-request-id","type":"string","value":"347edd6d-e273-953e-87f6-7ba07f352331"},{"key":"http.method","type":"string","value":"POST"},{"key":"http.status_code","type":"string","value":"200"},{"key":"http.url","type":"string","value":"http://frontend-proxy:8080/api/checkout"},{"key":"request_size","type":"string","value":"388"},{"key":"response_flags","type":"string","value":"-"},{"key":"response_size","type":"string","value":"857"},{"key":"upstream_cluster","type":"string","value":"frontend"},{"key":"upstream_cluster.name","type":"string","value":"frontend"},{"key":"user_agent","type":"string","value":"python-requests/2.32.4"},{"key":"error","type":"string","value":"unset"}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null}],"traceID":"9e06226196051d9c3c10dfab343791ad","warnings":null}],"errors":null,"limit":0,"offset":0,"total":1} ``` +#### Querying dependencies + +> This feature is **experimental**. To enable dependencies visualization, you **must** set `-servicegraph.enableTask` to `true` on VictoriaTraces single-node or +> vtstorage to run the background task, which generates service graph data periodically. See also: `-servicegraph.*` flags. + +The dependencies graph is available at the `/select/jaeger/api/dependencies` HTTP endpoint, which implemented the +[Jaeger service dependencies graph API](https://www.jaegertracing.io/docs/2.10/architecture/apis/#service-dependencies-graph). + +This endpoint provides the following params: +- `endTs`: the end timestamp in unix milliseconds. Current timestamp will be used if empty. +- `lookback`: the lookbehind window duration in milliseconds. Default to `1h` if empty. + +Here are examples of the dependencies API: + +1. Show dependencies within a time range: +```sh +curl http://:10428/select/jaeger/api/dependencies?endTs=1758213428616&lookback=60000 +``` + +Here's a response example: +```json +{"data":[{"parent":"shipping","child":"quote","callCount":2},{"parent":"checkout","child":"cart","callCount":4},{"parent":"frontend-proxy","child":"frontend","callCount":1193},{"parent":"cart","child":"flagd","callCount":2},{"parent":"checkout","child":"shipping","callCount":4},{"parent":"recommendation","child":"product-catalog","callCount":68},{"parent":"frontend","child":"cart","callCount":155},{"parent":"frontend","child":"recommendation","callCount":64},{"parent":"checkout","child":"product-catalog","callCount":6},{"parent":"checkout","child":"currency","callCount":8},{"parent":"checkout","child":"payment","callCount":2},{"parent":"frontend","child":"product-catalog","callCount":350},{"parent":"load-generator","child":"frontend-proxy","callCount":32},{"parent":"frontend-proxy","child":"image-provider","callCount":591},{"parent":"frontend-proxy","child":"flagd","callCount":118},{"parent":"frontend","child":"currency","callCount":141},{"parent":"frontend-web","child":"frontend-proxy","callCount":333},{"parent":"checkout","child":"email","callCount":2},{"parent":"frontend","child":"checkout","callCount":2}],"errors": null,"limit": 0,"offset": 0,"total":19} +``` + #### Tags Filter Examples The `/select/jaeger/api/traces` HTTP endpoint in VictoriaTraces provides `tags` param for filtering traces by **resource attributes**, diff --git a/lib/protoparser/opentelemetry/pb/internal_fields.go b/lib/protoparser/opentelemetry/pb/internal_fields.go new file mode 100644 index 000000000..8a6616c78 --- /dev/null +++ b/lib/protoparser/opentelemetry/pb/internal_fields.go @@ -0,0 +1,20 @@ +package pb + +// internal_fields.go contains the stream names/values, field names/values that VictoriaTraces required/generated. +// +// They're NOT part of the OpenTelemetry standard. + +// TraceID index stream and fields +const ( + TraceIDIndexStreamName = "trace_id_idx_stream" + TraceIDIndexFieldName = "trace_id_idx" + TraceIDIndexPartitionCount = uint64(1024) +) + +// service graph stream and fields +const ( + ServiceGraphStreamName = "trace_service_graph_stream" + ServiceGraphParentFieldName = "parent" + ServiceGraphChildFieldName = "child" + ServiceGraphCallCountFieldName = "callCount" +) diff --git a/lib/protoparser/opentelemetry/pb/trace_fields.go b/lib/protoparser/opentelemetry/pb/trace_fields.go index 9106607e1..5709c9e06 100644 --- a/lib/protoparser/opentelemetry/pb/trace_fields.go +++ b/lib/protoparser/opentelemetry/pb/trace_fields.go @@ -2,13 +2,6 @@ package pb // trace_fields.go contains field names when storing OTLP trace span data in VictoriaLogs. -// Special: TraceID index stream and fields -const ( - TraceIDIndexStreamName = "trace_id_idx_stream" - TraceIDIndexFieldName = "trace_id_idx" - TraceIDIndexPartitionCount = uint64(1024) -) - // Resource const ( ResourceAttrPrefix = "resource_attr:"