diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 31375626..f722e90d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -136,7 +136,7 @@ jobs:
TRACE_ID=$(grep "Location: /trace/" curl-output.http | cut -d/ -f3 | tr -d '\r')
mkdir -p output/api/traces
- for mode in ff{0,1,2,3}{0,1}00000{0,1}; do
+ for mode in ff{0,1,2,3}{0,1,5,6}000000; do
mode_trace=${mode}${TRACE_ID:10}
curl -o output/api/traces/$mode_trace http://localhost:16686/api/traces/$mode_trace
done
@@ -172,12 +172,12 @@ jobs:
Redirecting to ${basename}
-
+
EOF
- mkdir -p output/trace/ff20000000$(cat $trace_dir/trace_id)
- cp spa.html output/trace/ff20000000$(cat $trace_dir/trace_id)/index.html
+ mkdir -p output/trace/ff26000000$(cat $trace_dir/trace_id)
+ cp spa.html output/trace/ff26000000$(cat $trace_dir/trace_id)/index.html
cp $trace_dir/api/traces/* output/api/traces/
done
mv output/dot-usage/* output/
@@ -261,7 +261,7 @@ jobs:
promises.push((async () => {
const traceName = await fs.readFile(`pages/${trace}/trace_name`, {encoding: "utf8"})
const partialTraceId = await fs.readFile(`pages/${trace}/trace_id`, {encoding: "utf8"})
- const traceId = "ff20000000" + partialTraceId
+ const traceId = "ff26000000" + partialTraceId
console.log(`Loading trace ${traceName}`)
diff --git a/Makefile b/Makefile
index 6d92e630..5b928d5a 100644
--- a/Makefile
+++ b/Makefile
@@ -34,6 +34,8 @@ else
LOG_FILE_ARG ?=
endif
+LINKER_WORKER_COUNT ?= 1
+
CONTROLLERS ?= audit-consumer,audit-producer,audit-webhook,event-informer,annotation-linker,owner-linker,resource-object-tag,resource-event-tag,diff-decorator,diff-controller,diff-api,pprof,jaeger-storage-plugin,jaeger-redirect-server,kelemetrix
ifeq ($(CONTROLLERS),)
ENABLE_ARGS ?=
@@ -69,7 +71,7 @@ endif
.PHONY: run dump-rotate test usage dot kind stack pre-commit
run: output/kelemetry $(DUMP_ROTATE_DEP)
GIN_MODE=debug \
- ./output/kelemetry \
+ $(RUN_PREFIX) ./output/kelemetry $(RUN_SUFFIX) \
--mq=local \
--audit-consumer-partition=$(PARTITIONS) \
--http-address=0.0.0.0 \
@@ -85,6 +87,7 @@ run: output/kelemetry $(DUMP_ROTATE_DEP)
--log-file=$(LOG_FILE) \
--aggregator-pseudo-span-global-tags=runId=$(RUN_ID) \
--aggregator-event-span-global-tags=run=$(RUN_ID) \
+ --linker-worker-count=$(LINKER_WORKER_COUNT) \
--pprof-addr=:6030 \
--diff-cache=$(ETCD_OR_LOCAL) \
--diff-cache-etcd-endpoints=127.0.0.1:2379 \
@@ -95,6 +98,7 @@ run: output/kelemetry $(DUMP_ROTATE_DEP)
--span-cache-etcd-endpoints=127.0.0.1:2379 \
--tracer-otel-endpoint=$(OTEL_EXPORTER_OTLP_ENDPOINT) \
--tracer-otel-insecure \
+ --object-cache-size=16777216 \
--jaeger-cluster-names=$(CLUSTER_NAME) \
--jaeger-storage-plugin-address=0.0.0.0:17271 \
--jaeger-backend=jaeger-storage \
@@ -117,15 +121,15 @@ test:
go test -v -race -coverpkg=./pkg/... -coverprofile=coverage.out $(INTEGRATION_ARG) $(BUILD_ARGS) ./pkg/...
usage: output/kelemetry
- ./output/kelemetry --usage=USAGE.txt
+ $(RUN_PREFIX) ./output/kelemetry $(RUN_SUFFIX) --usage=USAGE.txt
dot: output/kelemetry
- ./output/kelemetry --dot=depgraph.dot
+ $(RUN_PREFIX) ./output/kelemetry $(RUN_SUFFIX) --dot=depgraph.dot
dot -Tpng depgraph.dot >depgraph.png
dot -Tsvg depgraph.dot >depgraph.svg
output/kelemetry: go.mod go.sum $(shell find -type f -name "*.go")
- go build -v $(RACE_ARG) -ldflags=$(LDFLAGS) -o $@ $(BUILD_ARGS) .
+ go build -v $(RACE_ARG) -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) -o $@ $(BUILD_ARGS) .
kind:
kind delete cluster --name tracetest
diff --git a/charts/kelemetry/templates/_helpers.yaml b/charts/kelemetry/templates/_helpers.yaml
index 2524c062..16ac93ee 100644
--- a/charts/kelemetry/templates/_helpers.yaml
+++ b/charts/kelemetry/templates/_helpers.yaml
@@ -111,6 +111,7 @@ span-cache-etcd-prefix: {{ .Values.aggregator.spanCache.etcd.prefix | toJson }}
{{- end }}
{{/* LINKERS */}}
+linker-worker-count: {{ .Values.linkers.workerCount }}
annotation-linker-enable: {{ .Values.linkers.annotation }}
owner-linker-enable: {{ .Values.linkers.ownerReference }}
diff --git a/charts/kelemetry/values.yaml b/charts/kelemetry/values.yaml
index c29405f5..1c1654fd 100644
--- a/charts/kelemetry/values.yaml
+++ b/charts/kelemetry/values.yaml
@@ -323,6 +323,9 @@ aggregator:
# Linkers associated objects together.
linkers:
+ # Maximum number of concurrent link jobs.
+ # Each link job runs each of the linkers for a single object in series.
+ workerCount: 8
# Enable the owner linker, which links objects based on native owner references.
ownerReference: true
# Enable the annotation linker, which links objects based on the `kelemetry.kubewharf.io/parent-link` annotation.
diff --git a/dev.docker-compose.yaml b/dev.docker-compose.yaml
index 8f66a2b3..c951a95d 100644
--- a/dev.docker-compose.yaml
+++ b/dev.docker-compose.yaml
@@ -54,6 +54,7 @@ services:
- 127.0.0.1:17272:17271
volumes:
- badger:/mnt/badger
+ restart: always
# Web frontend for raw trace database view.
jaeger-query-raw:
diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md
index ea1417fd..dc6b026b 100644
--- a/docs/USER_GUIDE.md
+++ b/docs/USER_GUIDE.md
@@ -20,8 +20,12 @@ The "Service" field selects one of the display modes:
Additional information is available in event tags.
- `timeline`: All events are displayed as children of the root object.
-By default, the whole trace is displayed, including parent and sibling objects of the searched object.
-Enabling the `exclusive` option limits the output to the subtree under the object matched in the search.
+By default, only the trace for a single object is displayed.
+More traces are available by configuration:
+
+- `full tree`: view the full tree from the deepest ancestor
+- `ancestors`: include transitive owners
+- `children`: include child objects
### Cluster
diff --git a/go.mod b/go.mod
index 0186acaf..8f84b9dc 100644
--- a/go.mod
+++ b/go.mod
@@ -7,23 +7,23 @@ require (
github.com/dlclark/regexp2 v1.10.0
github.com/gin-gonic/gin v1.9.1
github.com/go-logr/logr v1.2.4
- github.com/jaegertracing/jaeger v1.48.0
- github.com/pelletier/go-toml/v2 v2.0.9
+ github.com/jaegertracing/jaeger v1.49.0
+ github.com/pelletier/go-toml/v2 v2.1.0
github.com/prometheus/client_golang v1.16.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
go.etcd.io/etcd/client/v3 v3.5.9
- go.opentelemetry.io/otel v1.16.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
- go.opentelemetry.io/otel/sdk v1.16.0
- go.opentelemetry.io/otel/trace v1.16.0
- go.uber.org/zap v1.25.0
- google.golang.org/grpc v1.57.0
+ go.opentelemetry.io/otel v1.18.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0
+ go.opentelemetry.io/otel/sdk v1.18.0
+ go.opentelemetry.io/otel/trace v1.18.0
+ go.uber.org/zap v1.26.0
+ google.golang.org/grpc v1.58.1
k8s.io/api v0.28.1
- k8s.io/apimachinery v0.28.1
+ k8s.io/apimachinery v0.28.2
k8s.io/apiserver v0.28.1
k8s.io/client-go v0.28.1
k8s.io/klog/v2 v2.100.1
@@ -32,7 +32,7 @@ require (
require (
github.com/Shopify/sarama v1.38.1 // indirect
- github.com/apache/thrift v0.18.1 // indirect
+ github.com/apache/thrift v0.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
@@ -74,12 +74,12 @@ require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/hashicorp/go-plugin v1.4.10 // indirect
+ github.com/hashicorp/go-plugin v1.5.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
@@ -106,7 +106,6 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/olivere/elastic v6.2.37+incompatible // indirect
- github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -119,8 +118,6 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
- github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
- github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
@@ -128,24 +125,23 @@ require (
go.etcd.io/etcd/api/v3 v3.5.9 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
go.opencensus.io v0.24.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.1-0.20230612162650-64be7e574a17 // indirect
- go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
- go.opentelemetry.io/otel/metric v1.16.0 // indirect
- go.opentelemetry.io/proto/otlp v0.19.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0 // indirect
+ go.opentelemetry.io/otel/metric v1.18.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.3.0 // indirect
- golang.org/x/crypto v0.12.0 // indirect
- golang.org/x/net v0.14.0 // indirect
- golang.org/x/oauth2 v0.8.0 // indirect
- golang.org/x/sys v0.11.0 // indirect
- golang.org/x/term v0.11.0 // indirect
- golang.org/x/text v0.12.0 // indirect
+ golang.org/x/crypto v0.13.0 // indirect
+ golang.org/x/net v0.15.0 // indirect
+ golang.org/x/oauth2 v0.10.0 // indirect
+ golang.org/x/sys v0.12.0 // indirect
+ golang.org/x/term v0.12.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
+ google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index f9ff64bd..4011b2f8 100644
--- a/go.sum
+++ b/go.sum
@@ -17,14 +17,14 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
-cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
+cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
+cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
@@ -47,17 +47,16 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A=
github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g=
github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/thrift v0.18.1 h1:lNhK/1nqjbwbiOPDBPFJVKxgDEGSepKuTh6OLiXW8kg=
-github.com/apache/thrift v0.18.1/go.mod h1:rdQn/dCcDKEWjjylUeueum4vQEjG2v8v2PqriUnbr+I=
+github.com/apache/thrift v0.19.0 h1:sOqkWPzMj7w6XaYbJQG7m4sGqVolaW/0D28Ln7yPzMk=
+github.com/apache/thrift v0.19.0/go.mod h1:SUALL216IiaOw2Oy+5Vs9lboJ/t9g40C+G07Dc0QC1I=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@@ -80,11 +79,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/coocood/freecache v1.2.3 h1:lcBwpZrwBZRZyLk/8EMyQVXRiFl663cCuMOrjCALeto=
github.com/coocood/freecache v1.2.3/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
@@ -125,10 +119,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
+github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
@@ -141,7 +133,6 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
@@ -179,7 +170,6 @@ github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6x
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -209,7 +199,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -233,7 +222,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -262,10 +250,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -275,8 +261,8 @@ github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-plugin v1.4.10 h1:xUbmA4jC6Dq163/fWcp8P3JuHilrHHMLNRxzGQJ9hNk=
-github.com/hashicorp/go-plugin v1.4.10/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0=
+github.com/hashicorp/go-plugin v1.5.0 h1:g6Lj3USwF5LaB8HlvCxPjN2X4nFE08ko2BJNVpl7TIE=
+github.com/hashicorp/go-plugin v1.5.0/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -292,8 +278,8 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
-github.com/jaegertracing/jaeger v1.48.0 h1:YuKooQ7qJsjgxws9xuf8C/BLNTPx8qTAJz4wv7IHhSc=
-github.com/jaegertracing/jaeger v1.48.0/go.mod h1:BoAPkdCAIEuLsVz/EDhjXd+GSVpHtJhiGqWoFEvBCKg=
+github.com/jaegertracing/jaeger v1.49.0 h1:3XI8ZOK6oncyoAxCiKakC9sRaeDTdDwmxmaSN+KQBo8=
+github.com/jaegertracing/jaeger v1.49.0/go.mod h1:gU5wCSJSwk5MJHO/uWEBYKklT80oqEc2ixj3ssbwloQ=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -306,7 +292,7 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
+github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -364,14 +350,12 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U=
github.com/olivere/elastic v6.2.37+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
-github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
-github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
-github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
-github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -390,7 +374,6 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -436,10 +419,6 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
-github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
-github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
@@ -469,32 +448,29 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.1-0.20230612162650-64be7e574a17 h1:mdcNStUIXngF/mH3xxAo4nbR4g65IXqLL1SvYMjz7JQ=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.1-0.20230612162650-64be7e574a17/go.mod h1:N2Nw/UmmvQn0yCnaUzvsWzTWIeffYIdFteg6mxqCWII=
-go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
-go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
-go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0=
-go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo=
-go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
-go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
-go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
-go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
-go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
-go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
-go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
-go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
-go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0 h1:7XZai4VhA473clBrOqqHdjHBImGfyEtv0qW4nnn/kAo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0/go.mod h1:1WpsUwjQrUJSNugfMlPn0rPRJ9Do7wwBgTBPK7MLiS4=
+go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs=
+go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4=
+go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ=
+go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k=
+go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY=
+go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M=
+go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10=
+go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
-go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
@@ -508,8 +484,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
-golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -576,13 +552,12 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
-golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -592,9 +567,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
-golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
+golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -606,7 +580,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -641,10 +615,8 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -658,26 +630,25 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
-golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
-golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -788,7 +759,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -802,13 +772,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
-google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
-google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
-google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
+google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
+google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0=
+google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
+google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -822,15 +791,11 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
-google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
+google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58=
+google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -843,7 +808,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -857,7 +821,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -874,8 +837,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108=
k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg=
-k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY=
-k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw=
+k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
+k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
k8s.io/apiserver v0.28.1 h1:dw2/NKauDZCnOUAzIo2hFhtBRUo6gQK832NV8kuDbGM=
k8s.io/apiserver v0.28.1/go.mod h1:d8aizlSRB6yRgJ6PKfDkdwCy2DXt/d1FDR6iJN9kY1w=
k8s.io/client-go v0.28.1 h1:pRhMzB8HyLfVwpngWKE8hDcXRqifh1ga2Z/PU9SXVK8=
diff --git a/hack/tfconfig.yaml b/hack/tfconfig.yaml
index 2be80224..b8ddc198 100644
--- a/hack/tfconfig.yaml
+++ b/hack/tfconfig.yaml
@@ -13,9 +13,8 @@ configs:
- kind: Batch
batchName: initial
- kind: ExtractNestingVisitor
- matchesNestLevel:
- oneOf: []
- negate: true
+ matchesPseudoType:
+ oneOf: ["object"]
- kind: Batch
batchName: final
- id: "20000000"
@@ -23,12 +22,12 @@ configs:
steps:
- kind: Batch
batchName: initial
- - kind: ExtractNestingVisitor
- matchesNestLevel:
- oneOf: ["object"]
- negate: true
- kind: Batch
batchName: collapse
+ - kind: ExtractNestingVisitor
+ matchesPseudoType:
+ oneOf: ["linkClass"]
+ matchesName:
- kind: CompactDurationVisitor
- kind: Batch
batchName: final
@@ -37,28 +36,48 @@ configs:
steps:
- kind: Batch
batchName: initial
- - kind: ExtractNestingVisitor
- matchesNestLevel:
- oneOf: ["object"]
- negate: true
- kind: Batch
batchName: collapse
- kind: GroupByTraceSourceVisitor
shouldBeGrouped:
oneOf: ["event"]
- negate: true
+ then: false
- kind: CompactDurationVisitor
- kind: Batch
batchName: final
modifiers:
+ # Multiple active link-selector modifiers are additive (union)
"01000000":
- displayName: exclusive
- modifierName: exclusive
+ # the entire tree under the deepest (up to 3 levels) ancestor
+ displayName: full tree
+ modifierName: link-selector
+ args:
+ modifierClass: owner-ref
+ includeSiblings: false
+ upwardDistance: 3
+ downwardDistance: 3
+ "02000000":
+ # include all ancestors (up to 3) but not siblings of ancestors
+ displayName: ancestors
+ modifierName: link-selector
+ args:
+ modifierClass: owner-ref
+ includeSiblings: true
+ upwardDistance: 3
+ "04000000":
+ # the entire subtree under this object
+ displayName: owned objects
+ modifierName: link-selector
+ args:
+ modifierClass: owner-ref
+ ifAll:
+ - linkClass: children
+ fromChild: false
+ downwardDistance: 3
# Uncomment to enable extension trace from apiserver
# "00000001":
-# # We want to run extension modifiers after exclusive modifier to avoid fetching unused traces
# displayName: apiserver trace
# modifierName: extension
# args:
@@ -122,9 +141,6 @@ batches:
- name: collapse
steps:
- kind: CollapseNestingVisitor
- shouldCollapse:
- oneOf: []
- negate: true
tagMappings:
"audit":
- fromSpanTag: "userAgent"
diff --git a/pkg/aggregator/aggregator.go b/pkg/aggregator/aggregator.go
index 4c7c976c..ae7d5f73 100644
--- a/pkg/aggregator/aggregator.go
+++ b/pkg/aggregator/aggregator.go
@@ -21,13 +21,12 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
- "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"k8s.io/utils/clock"
"github.com/kubewharf/kelemetry/pkg/aggregator/aggregatorevent"
"github.com/kubewharf/kelemetry/pkg/aggregator/eventdecorator"
- "github.com/kubewharf/kelemetry/pkg/aggregator/linker"
+ linkjob "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job"
"github.com/kubewharf/kelemetry/pkg/aggregator/objectspandecorator"
"github.com/kubewharf/kelemetry/pkg/aggregator/spancache"
"github.com/kubewharf/kelemetry/pkg/aggregator/tracer"
@@ -42,14 +41,11 @@ func init() {
}
type options struct {
- reserveTtl time.Duration
- spanTtl time.Duration
- spanFollowTtl time.Duration
- spanExtraTtl time.Duration
- globalPseudoTags map[string]string
- globalEventTags map[string]string
- subObjectPrimaryPollInterval time.Duration
- subObjectPrimaryPollTimeout time.Duration
+ reserveTtl time.Duration
+ spanTtl time.Duration
+ spanExtraTtl time.Duration
+ globalPseudoTags map[string]string
+ globalEventTags map[string]string
}
func (options *options) Setup(fs *pflag.FlagSet) {
@@ -65,15 +61,10 @@ func (options *options) Setup(fs *pflag.FlagSet) {
time.Minute*30,
"duration of each span",
)
- fs.DurationVar(&options.spanFollowTtl,
- "aggregator-span-follow-ttl",
- 0,
- "duration after expiry of previous span within which new spans are considered FollowsFrom",
- )
fs.DurationVar(&options.spanExtraTtl,
"aggregator-span-extra-ttl",
0,
- "duration for which an object span is retained after the FollowsFrom period has ended",
+ "duration for which an object span is retained in cache after its duration has elapsed",
)
fs.StringToStringVar(&options.globalPseudoTags,
"aggregator-pseudo-span-global-tags",
@@ -85,17 +76,6 @@ func (options *options) Setup(fs *pflag.FlagSet) {
map[string]string{},
"tags applied to all event spans",
)
- fs.DurationVar(&options.subObjectPrimaryPollInterval,
- "aggregator-sub-object-primary-poll-interval",
- time.Second*5,
- "interval to poll primary event before promoting non-primary events",
- )
- fs.DurationVar(&options.subObjectPrimaryPollTimeout,
- "aggregator-sub-object-primary-poll-timeout",
- time.Second*5,
- "timeout to wait for primary event before promoting non-primary events "+
- "(increasing this timeout may lead to indefinite consumer lag",
- )
}
func (options *options) EnableFlag() *bool { return nil }
@@ -104,27 +84,36 @@ type Aggregator interface {
manager.Component
// Send sends an event to the tracer backend.
- // The sub-object ID is an optional identifier that associates the event with an object-scoped context (e.g. resource version).
- // If an event is created with the same sub-object ID with Primary=false,
- // it waits for the primary event to be created and takes it as the parent.
- // If the primary event does not get created after options.subObjectPrimaryBackoff, this event is promoted as primary.
- // If multiple primary events are sent, the slower one (by SpanCache-authoritative timing) is demoted.
- Send(ctx context.Context, object utilobject.Rich, event *aggregatorevent.Event, subObjectId *SubObjectId) error
-}
-
-type SubObjectId struct {
- Id string
- Primary bool
+ Send(ctx context.Context, object utilobject.Rich, event *aggregatorevent.Event) error
+
+ // EnsureObjectSpan creates a pseudospan for the object, and triggers any possible relevant linkers.
+ EnsureObjectSpan(
+ ctx context.Context,
+ object utilobject.Rich,
+ eventTime time.Time,
+ ) (tracer.SpanContext, error)
+
+ // GetOrCreatePseudoSpan creates a span following the pseudospan standard with the required tags.
+ GetOrCreatePseudoSpan(
+ ctx context.Context,
+ object utilobject.Rich,
+ pseudoType zconstants.PseudoTypeValue,
+ eventTime time.Time,
+ parent tracer.SpanContext,
+ followsFrom tracer.SpanContext,
+ extraTags map[string]string,
+ dedupId string,
+ ) (span tracer.SpanContext, isNew bool, err error)
}
type aggregator struct {
- options options
- Clock clock.Clock
- Linkers *manager.List[linker.Linker]
- Logger logrus.FieldLogger
- SpanCache spancache.Cache
- Tracer tracer.Tracer
- Metrics metrics.Client
+ options options
+ Clock clock.Clock
+ Logger logrus.FieldLogger
+ SpanCache spancache.Cache
+ Tracer tracer.Tracer
+ Metrics metrics.Client
+ LinkJobPublisher linkjob.Publisher
EventDecorators *manager.List[eventdecorator.Decorator]
ObjectSpanDecorators *manager.List[objectspandecorator.Decorator]
@@ -136,13 +125,10 @@ type aggregator struct {
}
type sendMetric struct {
- Cluster string
- TraceSource string
- HasSubObjectId bool
- Primary bool // whether the subObjectId is primary or not
- PrimaryChanged bool // whether the primary got demoted or non-primary got promoted
- Success bool
- Error metrics.LabeledError
+ Cluster string
+ TraceSource string
+ Success bool
+ Error metrics.LabeledError
}
func (*sendMetric) MetricName() string { return "aggregator_send" }
@@ -155,8 +141,9 @@ type sinceEventMetric struct {
func (*sinceEventMetric) MetricName() string { return "aggregator_send_since_event" }
type lazySpanMetric struct {
- Cluster string
- Result string
+ Cluster string
+ PseudoType zconstants.PseudoTypeValue
+ Result string
}
func (*lazySpanMetric) MetricName() string { return "aggregator_lazy_span" }
@@ -169,13 +156,7 @@ func (aggregator *aggregator) Options() manager.Options {
return &aggregator.options
}
-func (aggregator *aggregator) Init() error {
- if aggregator.options.spanFollowTtl > aggregator.options.spanTtl {
- return fmt.Errorf("invalid option: --span-ttl must not be shorter than --span-follow-ttl")
- }
-
- return nil
-}
+func (aggregator *aggregator) Init() error { return nil }
func (aggregator *aggregator) Start(ctx context.Context) error { return nil }
@@ -185,7 +166,6 @@ func (aggregator *aggregator) Send(
ctx context.Context,
object utilobject.Rich,
event *aggregatorevent.Event,
- subObjectId *SubObjectId,
) (err error) {
sendMetric := &sendMetric{Cluster: object.Cluster, TraceSource: event.TraceSource}
defer aggregator.SendMetric.DeferCount(aggregator.Clock.Now(), sendMetric)
@@ -194,137 +174,28 @@ func (aggregator *aggregator) Send(
With(&sinceEventMetric{Cluster: object.Cluster, TraceSource: event.TraceSource}).
Summary(float64(aggregator.Clock.Since(event.Time).Nanoseconds()))
- var parentSpan tracer.SpanContext
-
- type primaryReservation struct {
- cacheKey string
- uid spancache.Uid
- }
- var reservedPrimary *primaryReservation
-
- if subObjectId != nil {
- sendMetric.HasSubObjectId = true
- sendMetric.Primary = subObjectId.Primary
-
- cacheKey := aggregator.spanCacheKey(object, subObjectId.Id)
-
- if !subObjectId.Primary {
- pollCtx, cancelFunc := context.WithTimeout(ctx, aggregator.options.subObjectPrimaryPollTimeout)
- defer cancelFunc()
-
- if err := wait.PollUntilContextCancel(
- pollCtx,
- aggregator.options.subObjectPrimaryPollInterval,
- true,
- func(context.Context) (done bool, err error) {
- entry, err := aggregator.SpanCache.Fetch(pollCtx, cacheKey)
- if err != nil {
- sendMetric.Error = metrics.LabelError(err, "PrimaryEventPoll")
- return false, fmt.Errorf("%w during primary event poll", err)
- }
-
- if entry != nil {
- parentSpan, err = aggregator.Tracer.ExtractCarrier(entry.Value)
- if err != nil {
- sendMetric.Error = metrics.LabelError(err, "ExtractPrimaryCarrier")
- return false, fmt.Errorf("%w during decoding primary span", err)
- }
-
- return true, nil
- }
-
- return false, nil
- },
- ); err != nil {
- if !wait.Interrupted(err) {
- if sendMetric.Error == nil {
- sendMetric.Error = metrics.LabelError(err, "UnknownPrimaryPoll")
- aggregator.Logger.
- WithFields(object.AsFields("object")).
- WithField("event", event.Title).
- WithError(err).
- Warn("Unknown error for primary poll")
- }
- return err
- }
-
- sendMetric.PrimaryChanged = parentSpan == nil
-
- // primary poll timeout, parentSpan == nil, so promote to primary
- sendMetric.Error = nil
- }
- }
-
- if parentSpan == nil {
- // either object ID is primary, or primary poll expired, in which case we should promote
- if err := retry.OnError(retry.DefaultBackoff, spancache.ShouldRetry, func() error {
- entry, err := aggregator.SpanCache.FetchOrReserve(ctx, cacheKey, aggregator.options.reserveTtl)
- if err != nil {
- sendMetric.Error = metrics.LabelError(err, "PrimaryReserve")
- return fmt.Errorf("%w during primary event fetch-or-reserve", err)
- }
-
- if entry.Value != nil {
- // another primary event was sent, demote this one
- sendMetric.PrimaryChanged = true
- event.Log(
- zconstants.LogTypeRealError,
- fmt.Sprintf("Kelemetry: multiple primary events for %s sent, demoted later event", subObjectId.Id),
- )
-
- parentSpan, err = aggregator.Tracer.ExtractCarrier(entry.Value)
- if err != nil {
- sendMetric.Error = metrics.LabelError(err, "ExtractAltPrimaryCarrier")
- return fmt.Errorf("%w during decoding primary span", err)
- }
-
- return nil
- }
-
- reservedPrimary = &primaryReservation{
- cacheKey: cacheKey,
- uid: entry.LastUid,
- }
-
- return nil
- }); err != nil {
- if wait.Interrupted(err) {
- sendMetric.Error = metrics.LabelError(err, "PrimaryReserveTimeout")
- }
- return err
- }
- }
- }
-
- if parentSpan == nil {
- // there is no primary span to fallback to, so we are the primary
- parentSpan, err = aggregator.ensureObjectSpan(ctx, object, event.Time)
- if err != nil {
- sendMetric.Error = metrics.LabelError(err, "EnsureObjectSpan")
- return fmt.Errorf("%w during fetching field span for primary span", err)
- }
+ parentSpan, err := aggregator.EnsureObjectSpan(ctx, object, event.Time)
+ if err != nil {
+ sendMetric.Error = metrics.LabelError(err, "EnsureObjectSpan")
+ return fmt.Errorf("%w during ensuring object span", err)
}
for _, decorator := range aggregator.EventDecorators.Impls {
decorator.Decorate(ctx, object, event)
}
+ tags := zconstants.VersionedKeyToSpanTags(object.VersionedKey)
+ tags[zconstants.NotPseudo] = zconstants.NotPseudo
+ tags[zconstants.TraceSource] = event.TraceSource
+
span := tracer.Span{
Type: event.TraceSource,
Name: event.Title,
StartTime: event.Time,
FinishTime: event.GetEndTime(),
Parent: parentSpan,
- Tags: map[string]string{
- "cluster": object.Cluster,
- "namespace": object.Namespace,
- "name": object.Name,
- "group": object.Group,
- "version": object.Version,
- "resource": object.Resource,
- zconstants.TraceSource: event.TraceSource,
- },
- Logs: event.Logs,
+ Tags: tags,
+ Logs: event.Logs,
}
for tagKey, tagValue := range event.Tags {
span.Tags[tagKey] = fmt.Sprint(tagValue)
@@ -333,31 +204,12 @@ func (aggregator *aggregator) Send(
span.Tags[tagKey] = tagValue
}
- sentSpan, err := aggregator.Tracer.CreateSpan(span)
+ _, err = aggregator.Tracer.CreateSpan(span)
if err != nil {
sendMetric.Error = metrics.LabelError(err, "CreateSpan")
return fmt.Errorf("cannot create span: %w", err)
}
- if reservedPrimary != nil {
- sentSpanRaw, err := aggregator.Tracer.InjectCarrier(sentSpan)
- if err != nil {
- sendMetric.Error = metrics.LabelError(err, "InjectCarrier")
- return fmt.Errorf("%w during serializing sent span ID", err)
- }
-
- if err := aggregator.SpanCache.SetReserved(
- ctx,
- reservedPrimary.cacheKey,
- sentSpanRaw,
- reservedPrimary.uid,
- aggregator.options.spanTtl,
- ); err != nil {
- sendMetric.Error = metrics.LabelError(err, "SetReserved")
- return fmt.Errorf("%w during persisting primary span ID", err)
- }
- }
-
sendMetric.Success = true
aggregator.Logger.WithFields(object.AsFields("object")).
@@ -368,211 +220,185 @@ func (aggregator *aggregator) Send(
return nil
}
-func (aggregator *aggregator) ensureObjectSpan(
+func (agg *aggregator) EnsureObjectSpan(
ctx context.Context,
object utilobject.Rich,
eventTime time.Time,
) (tracer.SpanContext, error) {
- return aggregator.getOrCreateSpan(ctx, object, eventTime, func() (_ tracer.SpanContext, err error) {
- // try to associate a parent object
- var parent *utilobject.Rich
-
- for _, linker := range aggregator.Linkers.Impls {
- parent = linker.Lookup(ctx, object)
- if parent != nil {
- break
- }
- }
+ span, isNew, err := agg.GetOrCreatePseudoSpan(ctx, object, zconstants.PseudoTypeObject, eventTime, nil, nil, nil, "object")
+ if err != nil {
+ return nil, err
+ }
- if parent == nil {
- return nil, nil
- }
+ if isNew {
+ agg.LinkJobPublisher.Publish(&linkjob.LinkJob{
+ Object: object,
+ EventTime: eventTime,
+ Span: span,
+ })
+ }
- // ensure parent object has a span
- return aggregator.ensureObjectSpan(ctx, *parent, eventTime)
- })
+ return span, nil
}
-func (aggregator *aggregator) getOrCreateSpan(
- ctx context.Context,
- object utilobject.Rich,
- eventTime time.Time,
- parentGetter func() (tracer.SpanContext, error),
-) (tracer.SpanContext, error) {
- lazySpanMetric := &lazySpanMetric{
- Cluster: object.Cluster,
- Result: "error",
- }
- defer aggregator.LazySpanMetric.DeferCount(aggregator.Clock.Now(), lazySpanMetric)
-
- cacheKey := aggregator.expiringSpanCacheKey(object, eventTime)
+type spanCreator struct {
+ cacheKey string
- logger := aggregator.Logger.
- WithField("step", "getOrCreateSpan").
- WithFields(object.AsFields("object"))
+ retries int32
+ fetchedSpan tracer.SpanContext
+ reserveUid spancache.Uid
+}
- var reserveUid spancache.Uid
- var returnSpan tracer.SpanContext
- var followsFrom tracer.SpanContext
+func (c *spanCreator) fetchOrReserve(
+ ctx context.Context,
+ agg *aggregator,
+) error {
+ c.retries += 1
- defer func() {
- logger.WithField("cacheKey", cacheKey).WithField("result", lazySpanMetric.Result).Debug("getOrCreateSpan")
- }()
+ entry, err := agg.SpanCache.FetchOrReserve(ctx, c.cacheKey, agg.options.reserveTtl)
+ if err != nil {
+ return metrics.LabelError(fmt.Errorf("%w during fetch-or-reserve of object span", err), "FetchOrReserve")
+ }
- retries := int64(0)
- if err := retry.OnError(retry.DefaultBackoff, spancache.ShouldRetry, func() error {
- retries += 1
- entry, err := aggregator.SpanCache.FetchOrReserve(ctx, cacheKey, aggregator.options.reserveTtl)
+ if entry.Value != nil {
+ // the entry already exists, no additional logic required
+ span, err := agg.Tracer.ExtractCarrier(entry.Value)
if err != nil {
- return metrics.LabelError(fmt.Errorf("%w during initial fetch-or-reserve", err), "FetchOrReserve")
+ return metrics.LabelError(fmt.Errorf("persisted span contains invalid data: %w", err), "BadCarrier")
}
- if entry.Value != nil {
- // the entry already exists, no additional logic required
- reserveUid = []byte{}
- followsFrom = nil
- returnSpan, err = aggregator.Tracer.ExtractCarrier(entry.Value)
- if err != nil {
- return metrics.LabelError(fmt.Errorf("persisted span contains invalid data: %w", err), "BadCarrier")
- }
-
- return nil
- }
-
- // we created a new reservation
- reserveUid = entry.LastUid
- returnSpan = nil
- followsFrom = nil
+ c.fetchedSpan = span
+ return nil
+ }
- // check if this new span is a follower of the previous one
- followsTime := eventTime.Add(-aggregator.options.spanFollowTtl)
- followsKey := aggregator.expiringSpanCacheKey(object, followsTime)
+ // else, a new reservation was created
+ c.reserveUid = entry.LastUid
+ return nil
+}
- if followsKey == cacheKey {
- // previous span expired
- return nil
- }
+func (agg *aggregator) GetOrCreatePseudoSpan(
+ ctx context.Context,
+ object utilobject.Rich,
+ pseudoType zconstants.PseudoTypeValue,
+ eventTime time.Time,
+ parent tracer.SpanContext,
+ followsFrom tracer.SpanContext,
+ extraTags map[string]string,
+ dedupId string,
+) (_span tracer.SpanContext, _isNew bool, _err error) {
+ lazySpanMetric := &lazySpanMetric{
+ Cluster: object.Cluster,
+ PseudoType: pseudoType,
+ Result: "error",
+ }
+ defer agg.LazySpanMetric.DeferCount(agg.Clock.Now(), lazySpanMetric)
- followsEntry, err := aggregator.SpanCache.Fetch(ctx, followsKey)
- if err != nil {
- return metrics.LabelError(fmt.Errorf("error fetching followed entry: %w", err), "FetchFollow")
- }
+ cacheKey := agg.expiringSpanCacheKey(object.Key, eventTime, dedupId)
- if followsEntry == nil {
- // no following target
- return nil
- }
+ logger := agg.Logger.
+ WithField("step", "GetOrCreatePseudoSpan").
+ WithField("dedupId", dedupId).
+ WithFields(object.AsFields("object"))
- if followsEntry.Value == nil {
- return metrics.LabelError(spancache.ErrAlreadyReserved, "FollowPending") // trigger retry
- }
+ defer func() {
+ logger.WithField("cacheKey", cacheKey).WithField("result", lazySpanMetric.Result).Debug("GetOrCreatePseudoSpan")
+ }()
- // we have a following target
- followsFrom, err = aggregator.Tracer.ExtractCarrier(followsEntry.Value)
- if err != nil {
- return metrics.LabelError(fmt.Errorf("followed persisted span contains invalid data: %w", err), "BadFollowCarrier")
- }
+ creator := &spanCreator{cacheKey: cacheKey}
- return nil
- }); err != nil {
- return nil, metrics.LabelError(fmt.Errorf("cannot reserve or fetch span %q: %w", cacheKey, err), "ReserveRetryLoop")
+ if err := retry.OnError(
+ retry.DefaultBackoff,
+ spancache.ShouldRetry,
+ func() error { return creator.fetchOrReserve(ctx, agg) },
+ ); err != nil {
+ return nil, false, metrics.LabelError(fmt.Errorf("cannot reserve or fetch span %q: %w", cacheKey, err), "ReserveRetryLoop")
}
retryCountMetric := lazySpanRetryCountMetric(*lazySpanMetric)
defer func() {
- aggregator.LazySpanRetryCountMetric.With(&retryCountMetric).Summary(float64(retries))
+ agg.LazySpanRetryCountMetric.With(&retryCountMetric).Summary(float64(creator.retries))
}() // take the value of lazySpanMetric later
logger = logger.
- WithField("returnSpan", returnSpan != nil).
- WithField("reserveUid", reserveUid).
- WithField("followsFrom", followsFrom != nil)
+ WithField("returnSpan", creator.fetchedSpan != nil).
+ WithField("reserveUid", creator.reserveUid)
- if returnSpan != nil {
+ if creator.fetchedSpan != nil {
lazySpanMetric.Result = "fetch"
- return returnSpan, nil
+ return creator.fetchedSpan, false, nil
}
// we have a new reservation, need to initialize it now
- startTime := aggregator.Clock.Now()
+ startTime := agg.Clock.Now()
- parent, err := parentGetter()
+ span, err := agg.CreatePseudoSpan(ctx, object, pseudoType, eventTime, parent, followsFrom, extraTags)
if err != nil {
- return nil, fmt.Errorf("cannot fetch parent object: %w", err)
+ return nil, false, metrics.LabelError(fmt.Errorf("cannot create span: %w", err), "CreateSpan")
}
- span, err := aggregator.createSpan(ctx, object, zconstants.NestLevelObject, eventTime, parent, followsFrom)
+ entryValue, err := agg.Tracer.InjectCarrier(span)
if err != nil {
- return nil, metrics.LabelError(fmt.Errorf("cannot create span: %w", err), "CreateSpan")
+ return nil, false, metrics.LabelError(fmt.Errorf("cannot serialize span context: %w", err), "InjectCarrier")
}
- entryValue, err := aggregator.Tracer.InjectCarrier(span)
+ totalTtl := agg.options.spanTtl + agg.options.spanExtraTtl
+ err = agg.SpanCache.SetReserved(ctx, cacheKey, entryValue, creator.reserveUid, totalTtl)
if err != nil {
- return nil, metrics.LabelError(fmt.Errorf("cannot serialize span context: %w", err), "InjectCarrier")
+ return nil, false, metrics.LabelError(fmt.Errorf("cannot persist reserved value: %w", err), "PersistCarrier")
}
- totalTtl := aggregator.options.spanTtl + aggregator.options.spanFollowTtl + aggregator.options.spanExtraTtl
- err = aggregator.SpanCache.SetReserved(ctx, cacheKey, entryValue, reserveUid, totalTtl)
- if err != nil {
- return nil, metrics.LabelError(fmt.Errorf("cannot persist reserved value: %w", err), "PersistCarrier")
- }
+ logger.WithField("duration", agg.Clock.Since(startTime)).Debug("Created new span")
- logger.WithField("duration", aggregator.Clock.Since(startTime)).Debug("Created new span")
+ lazySpanMetric.Result = "create"
- if followsFrom != nil {
- lazySpanMetric.Result = "renew"
- } else {
- lazySpanMetric.Result = "create"
- }
-
- return span, nil
+ return span, true, nil
}
-func (aggregator *aggregator) createSpan(
+func (agg *aggregator) CreatePseudoSpan(
ctx context.Context,
object utilobject.Rich,
- nestLevel string,
+ pseudoType zconstants.PseudoTypeValue,
eventTime time.Time,
parent tracer.SpanContext,
followsFrom tracer.SpanContext,
+ extraTags map[string]string,
) (tracer.SpanContext, error) {
- remainderSeconds := eventTime.Unix() % int64(aggregator.options.spanTtl.Seconds())
+ remainderSeconds := eventTime.Unix() % int64(agg.options.spanTtl.Seconds())
startTime := eventTime.Add(-time.Duration(remainderSeconds) * time.Second)
+
+ tags := zconstants.VersionedKeyToSpanTags(object.VersionedKey)
+ tags[zconstants.TraceSource] = zconstants.TraceSourceObject
+ tags[zconstants.PseudoType] = string(pseudoType)
+ tags["timeStamp"] = startTime.Format(time.RFC3339)
+
span := tracer.Span{
- Type: nestLevel,
- Name: fmt.Sprintf("%s/%s %s", object.Resource, object.Name, nestLevel),
+ Type: string(pseudoType),
+ Name: fmt.Sprintf("%s/%s", object.Resource, object.Name),
StartTime: startTime,
- FinishTime: startTime.Add(aggregator.options.spanTtl),
+ FinishTime: startTime.Add(agg.options.spanTtl),
Parent: parent,
Follows: followsFrom,
- Tags: map[string]string{
- "cluster": object.Cluster,
- "namespace": object.Namespace,
- "name": object.Name,
- "group": object.Group,
- "version": object.Version,
- "resource": object.Resource,
- zconstants.NestLevel: nestLevel,
- zconstants.TraceSource: zconstants.TraceSourceObject,
- "timeStamp": startTime.Format(time.RFC3339),
- },
+ Tags: tags,
}
- for tagKey, tagValue := range aggregator.options.globalPseudoTags {
+ for tagKey, tagValue := range agg.options.globalPseudoTags {
+ span.Tags[tagKey] = tagValue
+ }
+ for tagKey, tagValue := range extraTags {
span.Tags[tagKey] = tagValue
}
- if nestLevel == zconstants.NestLevelObject {
- for _, decorator := range aggregator.ObjectSpanDecorators.Impls {
+ if pseudoType == zconstants.PseudoTypeObject {
+ for _, decorator := range agg.ObjectSpanDecorators.Impls {
decorator.Decorate(ctx, object, span.Type, span.Tags)
}
}
- spanContext, err := aggregator.Tracer.CreateSpan(span)
+ spanContext, err := agg.Tracer.CreateSpan(span)
if err != nil {
return nil, metrics.LabelError(fmt.Errorf("cannot create span: %w", err), "CreateSpan")
}
- aggregator.Logger.
+ agg.Logger.
WithFields(object.AsFields("object")).
WithField("parent", parent).
Debug("CreateSpan")
@@ -580,11 +406,15 @@ func (aggregator *aggregator) createSpan(
return spanContext, nil
}
-func (aggregator *aggregator) expiringSpanCacheKey(object utilobject.Rich, timestamp time.Time) string {
+func (aggregator *aggregator) expiringSpanCacheKey(
+ object utilobject.Key,
+ timestamp time.Time,
+ subObject string,
+) string {
expiringWindow := timestamp.Unix() / int64(aggregator.options.spanTtl.Seconds())
- return aggregator.spanCacheKey(object, fmt.Sprintf("field=object,window=%d", expiringWindow))
+ return aggregator.spanCacheKey(object, fmt.Sprintf("field=%s,window=%d", subObject, expiringWindow))
}
-func (aggregator *aggregator) spanCacheKey(object utilobject.Rich, subObjectId string) string {
- return fmt.Sprintf("%s/%s", object.String(), subObjectId)
+func (aggregator *aggregator) spanCacheKey(object utilobject.Key, window string) string {
+ return fmt.Sprintf("%s/%s", object.String(), window)
}
diff --git a/pkg/aggregator/linker/job/interface.go b/pkg/aggregator/linker/job/interface.go
new file mode 100644
index 00000000..8c9979ca
--- /dev/null
+++ b/pkg/aggregator/linker/job/interface.go
@@ -0,0 +1,63 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package linkjob
+
+import (
+ "context"
+ "time"
+
+ "github.com/kubewharf/kelemetry/pkg/aggregator/tracer"
+ "github.com/kubewharf/kelemetry/pkg/manager"
+ utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+)
+
+type Publisher interface {
+ Publish(job *LinkJob)
+}
+
+type Subscriber interface {
+ Subscribe(ctx context.Context, name string) <-chan *LinkJob
+}
+
+func init() {
+ manager.Global.Provide("linker-job-publisher", manager.Ptr[Publisher](&publisherMux{
+ Mux: manager.NewMux("linker-job-publisher", false),
+ }))
+ manager.Global.Provide("linker-job-subscriber", manager.Ptr[Subscriber](&subscriberMux{
+ Mux: manager.NewMux("linker-job-subscriber", false),
+ }))
+}
+
+type publisherMux struct {
+ *manager.Mux
+}
+
+func (mux *publisherMux) Publish(job *LinkJob) {
+ mux.Impl().(Publisher).Publish(job)
+}
+
+type subscriberMux struct {
+ *manager.Mux
+}
+
+func (mux *subscriberMux) Subscribe(ctx context.Context, name string) <-chan *LinkJob {
+ return mux.Impl().(Subscriber).Subscribe(ctx, name)
+}
+
+type LinkJob struct {
+ Object utilobject.Rich
+ EventTime time.Time
+ Span tracer.SpanContext
+}
diff --git a/pkg/aggregator/linker/job/local/local.go b/pkg/aggregator/linker/job/local/local.go
new file mode 100644
index 00000000..47310d5d
--- /dev/null
+++ b/pkg/aggregator/linker/job/local/local.go
@@ -0,0 +1,107 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package linkerjoblocal
+
+import (
+ "context"
+ "sync"
+
+ "github.com/sirupsen/logrus"
+
+ linkjob "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job"
+ "github.com/kubewharf/kelemetry/pkg/manager"
+ "github.com/kubewharf/kelemetry/pkg/metrics"
+ "github.com/kubewharf/kelemetry/pkg/util/channel"
+)
+
+func init() {
+ manager.Global.Provide("linker-job-local-queue", manager.Ptr(&queue{}))
+ manager.Global.ProvideMuxImpl("linker-job-local-publisher", manager.Ptr(&publisher{}), linkjob.Publisher.Publish)
+ manager.Global.ProvideMuxImpl("linker-job-local-subscriber", manager.Ptr(&subscriber{}), linkjob.Subscriber.Subscribe)
+}
+
+type queue struct {
+ Logger logrus.FieldLogger
+
+ subscribersMu sync.RWMutex
+ subscribers map[*subscriberKey]*channel.UnboundedQueue[*linkjob.LinkJob]
+}
+
+type subscriberKey struct{}
+
+func (queue *queue) Options() manager.Options { return &manager.NoOptions{} }
+func (queue *queue) Init() error {
+ queue.subscribers = map[*subscriberKey]*channel.UnboundedQueue[*linkjob.LinkJob]{}
+ return nil
+}
+func (queue *queue) Start(ctx context.Context) error { return nil }
+func (queue *queue) Close(ctx context.Context) error { return nil }
+
+type publisher struct {
+ Queue *queue
+ manager.MuxImplBase
+}
+
+func (publisher *publisher) Options() manager.Options { return &manager.NoOptions{} }
+func (publisher *publisher) Init() error { return nil }
+func (publisher *publisher) Start(ctx context.Context) error { return nil }
+func (publisher *publisher) Close(ctx context.Context) error { return nil }
+func (publisher *publisher) MuxImplName() (name string, isDefault bool) { return "local", true }
+func (publisher *publisher) Publish(job *linkjob.LinkJob) {
+ publisher.Queue.subscribersMu.RLock()
+ defer publisher.Queue.subscribersMu.RUnlock()
+
+ for _, sub := range publisher.Queue.subscribers {
+ sub.Send(job)
+ }
+}
+
+type subscriber struct {
+ Queue *queue
+ Metrics metrics.Client
+ manager.MuxImplBase
+}
+
+type queueMetricTags struct {
+ Name string
+}
+
+func (*queueMetricTags) MetricName() string { return "linker_local_worker_lag" }
+
+func (subscriber *subscriber) Options() manager.Options { return &manager.NoOptions{} }
+func (subscriber *subscriber) Init() error { return nil }
+func (subscriber *subscriber) Start(ctx context.Context) error { return nil }
+func (subscriber *subscriber) Close(ctx context.Context) error { return nil }
+func (subscriber *subscriber) MuxImplName() (name string, isDefault bool) { return "local", true }
+func (subscriber *subscriber) Subscribe(ctx context.Context, name string) <-chan *linkjob.LinkJob {
+ queue := channel.NewUnboundedQueue[*linkjob.LinkJob](16)
+ channel.InitMetricLoop(queue, subscriber.Metrics, &queueMetricTags{Name: name})
+
+ subscriber.Queue.subscribersMu.Lock()
+ defer subscriber.Queue.subscribersMu.Unlock()
+
+ key := &subscriberKey{}
+ subscriber.Queue.subscribers[key] = queue
+
+ go func() {
+ <-ctx.Done()
+
+ subscriber.Queue.subscribersMu.Lock()
+ defer subscriber.Queue.subscribersMu.Unlock()
+ delete(subscriber.Queue.subscribers, key)
+ }()
+
+ return queue.Receiver()
+}
diff --git a/pkg/aggregator/linker/job/worker/worker.go b/pkg/aggregator/linker/job/worker/worker.go
new file mode 100644
index 00000000..c5ee82d8
--- /dev/null
+++ b/pkg/aggregator/linker/job/worker/worker.go
@@ -0,0 +1,168 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package linkjobworker
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/pflag"
+ "k8s.io/utils/clock"
+ "k8s.io/utils/pointer"
+
+ "github.com/kubewharf/kelemetry/pkg/aggregator"
+ "github.com/kubewharf/kelemetry/pkg/aggregator/linker"
+ linkjob "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job"
+ "github.com/kubewharf/kelemetry/pkg/manager"
+ "github.com/kubewharf/kelemetry/pkg/metrics"
+ "github.com/kubewharf/kelemetry/pkg/util/shutdown"
+ "github.com/kubewharf/kelemetry/pkg/util/zconstants"
+)
+
+func init() {
+ manager.Global.Provide("linker-job-worker", manager.Ptr(&worker{}))
+}
+
+type workerOptions struct {
+ WorkerCount int
+}
+
+func (options *workerOptions) Setup(fs *pflag.FlagSet) {
+ fs.IntVar(&options.WorkerCount, "linker-worker-count", 0, "Number of workers to execute link jobs")
+}
+func (options *workerOptions) EnableFlag() *bool { return pointer.Bool(options.WorkerCount > 0) }
+
+type worker struct {
+ options workerOptions
+ Logger logrus.FieldLogger
+ Clock clock.Clock
+ Linkers *manager.List[linker.Linker]
+ Subscriber linkjob.Subscriber
+ Aggregator aggregator.Aggregator
+ ExecuteJobMetric *metrics.Metric[*executeJobMetric]
+
+ ch <-chan *linkjob.LinkJob
+}
+
+type executeJobMetric struct {
+ Linker string
+ Error metrics.LabeledError
+}
+
+func (*executeJobMetric) MetricName() string { return "linker_job_exec" }
+
+func (worker *worker) Options() manager.Options { return &worker.options }
+func (worker *worker) Init() error {
+ worker.ch = worker.Subscriber.Subscribe(context.Background(), "worker") // background context, never unsubscribe
+ return nil
+}
+
+func (worker *worker) Start(ctx context.Context) error {
+ for workerId := 0; workerId < worker.options.WorkerCount; workerId++ {
+ go func(workerId int) {
+ defer shutdown.RecoverPanic(worker.Logger)
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case job := <-worker.ch:
+ worker.executeJob(ctx, worker.Logger.WithFields(job.Object.AsFields("job")), job)
+ }
+ }
+ }(workerId)
+ }
+
+ return nil
+}
+func (worker *worker) Close(ctx context.Context) error { return nil }
+
+func (worker *worker) executeJob(ctx context.Context, logger logrus.FieldLogger, job *linkjob.LinkJob) {
+ for _, linker := range worker.Linkers.Impls {
+ linkerLogger := logger.WithField("linker", linker.LinkerName())
+ if err := worker.execute(ctx, linkerLogger, linker, job); err != nil {
+ linkerLogger.WithError(err).Error("generating links")
+ }
+ }
+}
+
+func (worker *worker) execute(ctx context.Context, logger logrus.FieldLogger, linker linker.Linker, job *linkjob.LinkJob) error {
+ logger.Debug("execute linker")
+ startTime := worker.Clock.Now()
+ links, err := linker.Lookup(ctx, job.Object)
+ worker.ExecuteJobMetric.DeferCount(startTime, &executeJobMetric{
+ Linker: linker.LinkerName(),
+ Error: err,
+ })
+ if err != nil {
+ return metrics.LabelError(fmt.Errorf("calling linker: %w", err), "CallLinker")
+ }
+
+ for _, link := range links {
+ linkedSpan, err := worker.Aggregator.EnsureObjectSpan(ctx, link.Object, job.EventTime)
+ if err != nil {
+ return metrics.LabelError(fmt.Errorf("creating object span: %w", err), "CreateLinkedObjectSpan")
+ }
+
+ forwardTags := map[string]string{}
+ zconstants.TagLinkedObject(forwardTags, zconstants.LinkRef{
+ Key: link.Object.Key,
+ Role: link.Role,
+ Class: link.Class,
+ })
+ _, _, err = worker.Aggregator.GetOrCreatePseudoSpan(
+ ctx,
+ job.Object,
+ zconstants.PseudoTypeLink,
+ job.EventTime,
+ job.Span,
+ nil,
+ forwardTags,
+ link.DedupId,
+ )
+ if err != nil {
+ return metrics.LabelError(
+ fmt.Errorf("creating link span from source object to linked object: %w", err),
+ "CreateForwardLinkSpan",
+ )
+ }
+
+ backwardTags := map[string]string{}
+ zconstants.TagLinkedObject(backwardTags, zconstants.LinkRef{
+ Key: job.Object.Key,
+ Role: zconstants.ReverseLinkRole(link.Role),
+ Class: link.Class,
+ })
+ _, _, err = worker.Aggregator.GetOrCreatePseudoSpan(
+ ctx,
+ link.Object,
+ zconstants.PseudoTypeLink,
+ job.EventTime,
+ linkedSpan,
+ nil,
+ backwardTags,
+ fmt.Sprintf("%s@%s", link.DedupId, job.Object.String()),
+ )
+ if err != nil {
+ return metrics.LabelError(
+ fmt.Errorf("creating link span from linked object to source object: %w", err),
+ "CreateBackwardLinkSpan",
+ )
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/aggregator/linker/linker.go b/pkg/aggregator/linker/linker.go
index f648be25..2bf517ee 100644
--- a/pkg/aggregator/linker/linker.go
+++ b/pkg/aggregator/linker/linker.go
@@ -18,8 +18,17 @@ import (
"context"
utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+ "github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
type Linker interface {
- Lookup(ctx context.Context, object utilobject.Rich) *utilobject.Rich
+ LinkerName() string
+ Lookup(ctx context.Context, object utilobject.Rich) ([]LinkerResult, error)
+}
+
+type LinkerResult struct {
+ Object utilobject.Rich
+ Role zconstants.LinkRoleValue
+ Class string
+ DedupId string
}
diff --git a/pkg/annotationlinker/linker.go b/pkg/annotationlinker/linker.go
index 46841d36..33707d05 100644
--- a/pkg/annotationlinker/linker.go
+++ b/pkg/annotationlinker/linker.go
@@ -17,6 +17,7 @@ package annotationlinker
import (
"context"
"encoding/json"
+ "fmt"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
@@ -26,7 +27,9 @@ import (
"github.com/kubewharf/kelemetry/pkg/k8s/discovery"
"github.com/kubewharf/kelemetry/pkg/k8s/objectcache"
"github.com/kubewharf/kelemetry/pkg/manager"
+ "github.com/kubewharf/kelemetry/pkg/metrics"
utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+ "github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
func init() {
@@ -58,7 +61,8 @@ func (ctrl *controller) Init() error { return nil }
func (ctrl *controller) Start(ctx context.Context) error { return nil }
func (ctrl *controller) Close(ctx context.Context) error { return nil }
-func (ctrl *controller) Lookup(ctx context.Context, object utilobject.Rich) *utilobject.Rich {
+func (ctrl *controller) LinkerName() string { return "annotation-linker" }
+func (ctrl *controller) Lookup(ctx context.Context, object utilobject.Rich) ([]linker.LinkerResult, error) {
raw := object.Raw
logger := ctrl.Logger.WithFields(object.AsFields("object"))
@@ -70,13 +74,12 @@ func (ctrl *controller) Lookup(ctx context.Context, object utilobject.Rich) *uti
raw, err = ctrl.ObjectCache.Get(ctx, object.VersionedKey)
if err != nil {
- logger.WithError(err).Error("cannot fetch object value")
- return nil
+ return nil, metrics.LabelError(fmt.Errorf("cannot fetch object value: %w", err), "FetchCache")
}
if raw == nil {
logger.Debug("object no longer exists")
- return nil
+ return nil, nil
}
}
@@ -84,8 +87,7 @@ func (ctrl *controller) Lookup(ctx context.Context, object utilobject.Rich) *uti
ref := &ParentLink{}
err := json.Unmarshal([]byte(ann), ref)
if err != nil {
- logger.WithError(err).Error("cannot parse ParentLink annotation")
- return nil
+ return nil, metrics.LabelError(fmt.Errorf("cannot parse ParentLink annotation: %w", err), "ParseAnnotation")
}
if ref.Cluster == "" {
@@ -93,10 +95,14 @@ func (ctrl *controller) Lookup(ctx context.Context, object utilobject.Rich) *uti
}
objectRef := ref.ToRich()
- logger.WithField("parent", objectRef).Debug("Resolved parent")
+ logger.WithFields(objectRef.AsFields("parent")).Debug("Resolved parent")
- return &objectRef
+ return []linker.LinkerResult{{
+ Object: objectRef,
+ Role: zconstants.LinkRoleParent,
+ DedupId: "annotation",
+ }}, nil
}
- return nil
+ return nil, nil
}
diff --git a/pkg/audit/consumer/consumer.go b/pkg/audit/consumer/consumer.go
index a937cb70..38519834 100644
--- a/pkg/audit/consumer/consumer.go
+++ b/pkg/audit/consumer/consumer.go
@@ -274,15 +274,7 @@ func (recv *receiver) handleItem(
Resource: objectRef.Resource,
}).Summary(float64(e2eLatency.Nanoseconds()))
- var subObjectId *aggregator.SubObjectId
- if recv.options.enableSubObject && (message.Verb == audit.VerbUpdate || message.Verb == audit.VerbPatch) {
- subObjectId = &aggregator.SubObjectId{
- Id: fmt.Sprintf("rv=%s", message.ObjectRef.ResourceVersion),
- Primary: message.ResponseStatus.Code < 300,
- }
- }
-
- err := recv.Aggregator.Send(ctx, objectRef, event, subObjectId)
+ err := recv.Aggregator.Send(ctx, objectRef, event)
if err != nil {
fieldLogger.WithError(err).Error()
} else {
diff --git a/pkg/event/controller.go b/pkg/event/controller.go
index e77fd5b6..669e1664 100644
--- a/pkg/event/controller.go
+++ b/pkg/event/controller.go
@@ -324,7 +324,7 @@ func (ctrl *controller) handleEvent(ctx context.Context, event *corev1.Event) {
Version: gvr.Version,
},
Uid: event.InvolvedObject.UID,
- }, aggregatorEvent, nil); err != nil {
+ }, aggregatorEvent); err != nil {
logger.WithError(err).Error("Cannot send trace")
metric.Error = metrics.LabelError(err, "SendTrace")
return
diff --git a/pkg/frontend/backend/interface.go b/pkg/frontend/backend/interface.go
index 82aedcff..c447cc79 100644
--- a/pkg/frontend/backend/interface.go
+++ b/pkg/frontend/backend/interface.go
@@ -23,6 +23,7 @@ import (
"github.com/jaegertracing/jaeger/storage/spanstore"
"k8s.io/utils/clock"
+ tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
"github.com/kubewharf/kelemetry/pkg/manager"
"github.com/kubewharf/kelemetry/pkg/metrics"
)
@@ -38,7 +39,6 @@ type Backend interface {
List(
ctx context.Context,
query *spanstore.TraceQueryParameters,
- exclusive bool,
) ([]*TraceThumbnail, error)
// Gets the full tree of a trace based on the identifier returned from a prvious call to List.
@@ -59,9 +59,13 @@ type TraceThumbnail struct {
// Identifier is a serializable object that identifies the trace in GetTrace calls.
Identifier any
- Spans []*model.Span
+ Spans *tftree.SpanTree
}
+func (tt *TraceThumbnail) GetSpans() *tftree.SpanTree { return tt.Spans }
+func (tt *TraceThumbnail) GetMetadata() any { return tt.Identifier }
+func (tt *TraceThumbnail) FromThumbnail(src *TraceThumbnail) { *tt = *src }
+
type mux struct {
*manager.Mux
Clock clock.Clock
@@ -81,10 +85,9 @@ func (*getMetric) MetricName() string { return "jaeger_backend_get" }
func (mux *mux) List(
ctx context.Context,
query *spanstore.TraceQueryParameters,
- exclusive bool,
) ([]*TraceThumbnail, error) {
defer mux.ListMetric.DeferCount(mux.Clock.Now(), &listMetric{})
- return mux.Impl().(Backend).List(ctx, query, exclusive)
+ return mux.Impl().(Backend).List(ctx, query)
}
func (mux *mux) Get(
diff --git a/pkg/frontend/backend/jaeger-storage/backend.go b/pkg/frontend/backend/jaeger-storage/backend.go
index 8cea1ee2..dcad2262 100644
--- a/pkg/frontend/backend/jaeger-storage/backend.go
+++ b/pkg/frontend/backend/jaeger-storage/backend.go
@@ -31,13 +31,11 @@ import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.uber.org/zap"
- "k8s.io/apimachinery/pkg/util/sets"
jaegerbackend "github.com/kubewharf/kelemetry/pkg/frontend/backend"
tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
"github.com/kubewharf/kelemetry/pkg/manager"
utiljaeger "github.com/kubewharf/kelemetry/pkg/util/jaeger"
- utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
"github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
@@ -152,123 +150,44 @@ func (backend *Backend) Close(ctx context.Context) error { return nil }
func (backend *Backend) List(
ctx context.Context,
params *spanstore.TraceQueryParameters,
- exclusive bool,
) ([]*jaegerbackend.TraceThumbnail, error) {
- filterTags := map[string]string{}
- for key, val := range params.Tags {
- filterTags[key] = val
- }
- if len(params.OperationName) > 0 {
- filterTags["cluster"] = params.OperationName
- }
-
- // TODO support additional user-defined trace sources
- var traces []*model.Trace
+ traceThumbnails := []*jaegerbackend.TraceThumbnail{}
for _, traceSource := range zconstants.KnownTraceSources(false) {
- if len(traces) >= params.NumTraces {
+ if len(traceThumbnails) >= params.NumTraces {
break
}
newParams := &spanstore.TraceQueryParameters{
ServiceName: traceSource,
- Tags: filterTags,
+ Tags: params.Tags,
StartTimeMin: params.StartTimeMin,
StartTimeMax: params.StartTimeMax,
DurationMin: params.DurationMin,
DurationMax: params.DurationMax,
- NumTraces: params.NumTraces - len(traces),
+ NumTraces: params.NumTraces - len(traceThumbnails),
}
- newTraces, err := backend.reader.FindTraces(ctx, newParams)
+ traces, err := backend.reader.FindTraces(ctx, newParams)
if err != nil {
return nil, fmt.Errorf("find traces from backend err: %w", err)
}
- traces = append(traces, newTraces...)
- }
-
- var traceThumbnails []*jaegerbackend.TraceThumbnail
-
- // a stateful function that determines only returns true for each valid resultant root span the first time
- var deduplicator func(*model.Span) bool
- if exclusive {
- // exclusive mode, each object under trace should have a list entry
- type objectInTrace struct {
- traceId model.TraceID
- key utilobject.Key
- }
- seenObjects := sets.New[objectInTrace]()
- deduplicator = func(span *model.Span) bool {
- key, hasKey := utilobject.FromSpan(span)
- if !hasKey {
- return false // not a root
- }
-
- field, hasField := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel)
- if !hasField || field.VStr != zconstants.NestLevelObject {
- return false // not an object root
+ for _, trace := range traces {
+ if len(trace.Spans) == 0 {
+ continue
}
- fullKey := objectInTrace{
- traceId: span.TraceID,
- key: key,
- }
-
- if seenObjects.Has(fullKey) {
- return false // a known root
- }
+ tree := tftree.NewSpanTree(trace.Spans)
- for reqKey, reqValue := range filterTags {
- if value, exists := model.KeyValues(span.Tags).FindByKey(reqKey); !exists || value.VStr != reqValue {
- return false // not a matched root
- }
+ thumbnail := &jaegerbackend.TraceThumbnail{
+ Identifier: identifier{
+ TraceId: tree.Root.TraceID,
+ SpanId: tree.Root.SpanID,
+ },
+ Spans: tree,
}
- seenObjects.Insert(fullKey)
- return true
- }
- } else {
- // non exclusive mode, display full trace, so we want each full trace to display exactly once.
- seenTraces := sets.New[model.TraceID]()
- deduplicator = func(span *model.Span) bool {
- if len(span.References) > 0 {
- return false // we only want the root
- }
-
- if seenTraces.Has(span.TraceID) {
- return false
- }
-
- seenTraces.Insert(span.TraceID)
- return true
- }
- }
-
- for _, trace := range traces {
- if len(trace.Spans) == 0 {
- continue
- }
-
- for _, span := range trace.Spans {
- if deduplicator(span) {
- tree := tftree.NewSpanTree(trace.Spans)
- if err := tree.SetRoot(span.SpanID); err != nil {
- return nil, fmt.Errorf("unexpected SetRoot error for span ID from trace: %w", err)
- }
-
- thumbnail := &jaegerbackend.TraceThumbnail{
- Identifier: identifier{
- TraceId: span.TraceID,
- SpanId: span.SpanID,
- },
- Spans: tree.GetSpans(),
- }
- traceThumbnails = append(traceThumbnails, thumbnail)
-
- backend.Logger.WithField("ident", thumbnail.Identifier).
- WithField("filteredSpans", len(thumbnail.Spans)).
- Debug("matched trace")
- }
+ traceThumbnails = append(traceThumbnails, thumbnail)
}
}
diff --git a/pkg/frontend/http/trace/server.go b/pkg/frontend/http/trace/server.go
index c7799f90..980e8251 100644
--- a/pkg/frontend/http/trace/server.go
+++ b/pkg/frontend/http/trace/server.go
@@ -105,7 +105,11 @@ func (server *server) handleTrace(ctx *gin.Context, metric *requestMetric) (code
return 400, fmt.Errorf("invalid param %w", err)
}
- trace, code, err := server.findTrace(metric, "tracing (exclusive)", query)
+ if query.DisplayMode == "" {
+ query.DisplayMode = "tracing"
+ }
+
+ trace, code, err := server.findTrace(metric, query.DisplayMode, query)
if err != nil {
return code, err
}
@@ -149,12 +153,14 @@ func pruneTrace(trace *model.Trace, spanType string) {
}
type traceQuery struct {
- Cluster string `form:"cluster"`
- Resource string `form:"resource"`
- Namespace string `form:"namespace"`
- Name string `form:"name"`
- Ts string `form:"ts"`
- SpanType string `form:"span_type"`
+ Cluster string `form:"cluster"`
+ Resource string `form:"resource"`
+ Namespace string `form:"namespace"`
+ Name string `form:"name"`
+ Start string `form:"start"`
+ End string `form:"end"`
+ SpanType string `form:"span_type"`
+ DisplayMode string `form:"displayMode"`
}
func (server *server) findTrace(metric *requestMetric, serviceName string, query traceQuery) (trace *model.Trace, code int, err error) {
@@ -179,10 +185,16 @@ func (server *server) findTrace(metric *requestMetric, serviceName string, query
return nil, 404, fmt.Errorf("cluster %s not supported now", cluster)
}
- timestamp, err := time.Parse(time.RFC3339, query.Ts)
+ startTimestamp, err := time.Parse(time.RFC3339, query.Start)
+ if err != nil {
+ metric.Error = metrics.MakeLabeledError("InvalidTimestamp")
+ return nil, 400, fmt.Errorf("invalid timestamp for start param %w", err)
+ }
+
+ endTimestamp, err := time.Parse(time.RFC3339, query.End)
if err != nil {
metric.Error = metrics.MakeLabeledError("InvalidTimestamp")
- return nil, 400, fmt.Errorf("invalid timestamp for ts param %w", err)
+ return nil, 400, fmt.Errorf("invalid timestamp for end param %w", err)
}
tags := map[string]string{
@@ -197,8 +209,9 @@ func (server *server) findTrace(metric *requestMetric, serviceName string, query
ServiceName: serviceName,
OperationName: cluster,
Tags: tags,
- StartTimeMin: timestamp.Truncate(time.Minute * 30),
- StartTimeMax: timestamp.Truncate(time.Minute * 30).Add(time.Minute * 30),
+ StartTimeMin: startTimestamp,
+ StartTimeMax: endTimestamp,
+ NumTraces: 20,
}
traces, err := server.SpanReader.FindTraces(context.Background(), parameters)
if err != nil {
diff --git a/pkg/frontend/reader/merge.go b/pkg/frontend/reader/merge.go
deleted file mode 100644
index d35d5c38..00000000
--- a/pkg/frontend/reader/merge.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2023 The Kelemetry Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package jaegerreader
-
-import (
- "github.com/jaegertracing/jaeger/model"
- "k8s.io/apimachinery/pkg/util/sets"
-
- jaegerbackend "github.com/kubewharf/kelemetry/pkg/frontend/backend"
- utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
-)
-
-type mergeMap struct {
- ptrSet sets.Set[*mergeEntry]
- fromKeys map[utilobject.Key]*mergeEntry
-}
-
-type mergeEntry struct {
- keys sets.Set[utilobject.Key]
- identifiers []any
- spans []*model.Span
-}
-
-func singletonMerged(keys sets.Set[utilobject.Key], thumbnail *jaegerbackend.TraceThumbnail) *mergeEntry {
- return &mergeEntry{
- keys: keys,
- identifiers: []any{thumbnail.Identifier},
- spans: thumbnail.Spans,
- }
-}
-
-func (entry *mergeEntry) join(other *mergeEntry) {
- for key := range other.keys {
- entry.keys.Insert(key)
- }
-
- entry.identifiers = append(entry.identifiers, other.identifiers...)
- entry.spans = append(entry.spans, other.spans...)
-}
-
-// add a thumbnail with a preferred root key.
-func (m *mergeMap) add(keys sets.Set[utilobject.Key], thumbnail *jaegerbackend.TraceThumbnail) {
- entry := singletonMerged(keys.Clone(), thumbnail)
- m.ptrSet.Insert(entry)
-
- dups := sets.New[*mergeEntry]()
-
- for key := range keys {
- if prev, hasPrev := m.fromKeys[key]; hasPrev {
- dups.Insert(prev)
- }
- }
-
- for dup := range dups {
- entry.join(dup)
- m.ptrSet.Delete(dup)
- }
-
- for key := range entry.keys {
- // including all new and joined keys
- m.fromKeys[key] = entry
- }
-}
-
-func mergeSegments(thumbnails []*jaegerbackend.TraceThumbnail) []*mergeEntry {
- m := mergeMap{
- ptrSet: sets.New[*mergeEntry](),
- fromKeys: map[utilobject.Key]*mergeEntry{},
- }
-
- for _, thumbnail := range thumbnails {
- keys := utilobject.FromSpans(thumbnail.Spans)
- m.add(keys, thumbnail)
- }
-
- return m.ptrSet.UnsortedList()
-}
diff --git a/pkg/frontend/reader/merge/merge.go b/pkg/frontend/reader/merge/merge.go
new file mode 100644
index 00000000..88bb6bdb
--- /dev/null
+++ b/pkg/frontend/reader/merge/merge.go
@@ -0,0 +1,628 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package merge
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "sync/atomic"
+ "time"
+
+ "github.com/jaegertracing/jaeger/model"
+ "k8s.io/apimachinery/pkg/util/sets"
+
+ jaegerbackend "github.com/kubewharf/kelemetry/pkg/frontend/backend"
+ tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
+ tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
+ utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+ reflectutil "github.com/kubewharf/kelemetry/pkg/util/reflect"
+ "github.com/kubewharf/kelemetry/pkg/util/semaphore"
+ "github.com/kubewharf/kelemetry/pkg/util/zconstants"
+)
+
+type objKey = utilobject.Key
+
+type Merger[M any] struct {
+ objects map[objKey]*object[M]
+}
+
+type TraceWithMetadata[M any] struct {
+ Tree *tftree.SpanTree
+ Metadata M
+}
+
+type RawTree struct {
+ Tree *tftree.SpanTree
+}
+
+func (tr RawTree) GetSpans() *tftree.SpanTree { return tr.Tree }
+func (tr RawTree) GetMetadata() struct{} { return struct{}{} }
+func (tr RawTree) FromThumbnail(self *RawTree, tt *jaegerbackend.TraceThumbnail) {
+ self.Tree = tt.Spans
+}
+
+func (merger *Merger[M]) AddTraces(trees []TraceWithMetadata[M]) (_affected sets.Set[objKey], _err error) {
+ if merger.objects == nil {
+ merger.objects = make(map[objKey]*object[M])
+ }
+
+ affected := sets.New[objKey]()
+ for _, trace := range trees {
+ key := zconstants.ObjectKeyFromSpan(trace.Tree.Root)
+ affected.Insert(key)
+
+ if obj, hasPrev := merger.objects[key]; hasPrev {
+ if err := obj.merge(trace.Tree, trace.Metadata); err != nil {
+ return nil, err
+ }
+ } else {
+ obj, err := newObject[M](key, trace.Tree, trace.Metadata)
+ if err != nil {
+ return nil, err
+ }
+
+ merger.objects[key] = obj
+ }
+ }
+
+ for key := range affected {
+ merger.objects[key].identifyLinks()
+ }
+
+ return affected, nil
+}
+
+type followLinkPool[M any] struct {
+ sem *semaphore.Semaphore
+ knownKeys sets.Set[objKey]
+ lister ListFunc[M]
+ startTime, endTime time.Time
+ merger *Merger[M]
+}
+
+func (fl *followLinkPool[M]) scheduleFrom(obj *object[M], followLimit *atomic.Int32, linkSelector tfconfig.LinkSelector) {
+ admittedLinks := []TargetLink{}
+
+ for _, link := range obj.links {
+ if _, known := fl.knownKeys[link.Key]; known {
+ admittedLinks = append(admittedLinks, link)
+ continue
+ }
+ if followLimit.Add(-1) < 0 {
+ continue
+ }
+
+ parentKey, childKey, parentIsSource := obj.key, link.Key, true
+ if link.Role == zconstants.LinkRoleParent {
+ parentKey, childKey, parentIsSource = link.Key, obj.key, false
+ }
+
+ subSelector := linkSelector.Admit(parentKey, childKey, parentIsSource, link.Class)
+ if subSelector != nil {
+ admittedLinks = append(admittedLinks, link)
+ fl.knownKeys.Insert(link.Key)
+ fl.schedule(link.Key, subSelector, followLimit, int32(fl.endTime.Sub(fl.startTime)/(time.Minute*30)))
+ }
+ }
+
+ obj.links = admittedLinks
+}
+
+func (fl *followLinkPool[M]) schedule(key objKey, linkSelector tfconfig.LinkSelector, followLimit *atomic.Int32, traceLimit int32) {
+ fl.sem.Schedule(func(ctx context.Context) (semaphore.Publish, error) {
+ thumbnails, err := fl.lister(ctx, key, fl.startTime, fl.endTime, int(traceLimit))
+ if err != nil {
+ return nil, fmt.Errorf("fetching linked traces: %w", err)
+ }
+
+ return func() error {
+ affected, err := fl.merger.AddTraces(thumbnails)
+ if err != nil {
+ return err
+ }
+
+ for key := range affected {
+ fl.scheduleFrom(fl.merger.objects[key], followLimit, linkSelector)
+ }
+
+ return nil
+ }, nil
+ })
+}
+
+type ListFunc[M any] func(
+ ctx context.Context,
+ key objKey,
+ startTime, endTime time.Time,
+ limit int,
+) ([]TraceWithMetadata[M], error)
+
+func (merger *Merger[M]) FollowLinks(
+ ctx context.Context,
+ linkSelector tfconfig.LinkSelector,
+ startTime, endTime time.Time,
+ lister ListFunc[M],
+ concurrency int,
+ limit int32,
+ limitIsGlobal bool,
+) error {
+ fl := &followLinkPool[M]{
+ sem: semaphore.New(concurrency),
+ knownKeys: sets.New[objKey](),
+ lister: lister,
+ startTime: startTime,
+ endTime: endTime,
+ merger: merger,
+ }
+
+ for _, obj := range merger.objects {
+ fl.knownKeys.Insert(obj.key)
+ }
+
+ globalLimit := new(atomic.Int32)
+ globalLimit.Store(limit)
+
+ for _, obj := range merger.objects {
+ var remainingLimit *atomic.Int32
+ if limitIsGlobal {
+ remainingLimit = globalLimit
+ } else {
+ remainingLimit = new(atomic.Int32)
+ remainingLimit.Store(limit)
+ }
+
+ fl.scheduleFrom(obj, remainingLimit, linkSelector)
+ }
+
+ if err := fl.sem.Run(ctx); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (merger *Merger[M]) MergeTraces() ([]*MergeTree[M], error) {
+ abLinks := abLinkMap{}
+
+ for _, obj := range merger.objects {
+ for _, link := range obj.links {
+ abLink := abLinkFromTargetLink(obj.key, link)
+ abLinks.insert(abLink)
+ }
+ }
+
+ connectedComps := merger.findConnectedComponents(merger.objects, abLinks)
+ mergeTrees := make([]*MergeTree[M], 0, len(connectedComps))
+ for _, keys := range connectedComps {
+ var members []*object[M]
+ for _, key := range keys {
+ members = append(members, merger.objects[key])
+ }
+
+ mergeTree, err := newMergeTree(members, abLinks)
+ if err != nil {
+ return nil, err
+ }
+
+ mergeTrees = append(mergeTrees, mergeTree)
+ }
+
+ return mergeTrees, nil
+}
+
+type object[M any] struct {
+ key objKey
+ metadata []M
+ tree *tftree.SpanTree
+
+ links []TargetLink
+}
+
+func newObject[M any](key objKey, trace *tftree.SpanTree, metadata M) (*object[M], error) {
+ clonedTree, err := trace.Clone()
+ if err != nil {
+ return nil, fmt.Errorf("clone spans: %w", err)
+ }
+ obj := &object[M]{
+ key: key,
+ metadata: []M{metadata},
+ tree: clonedTree,
+ }
+ return obj, nil
+}
+
+func (obj *object[M]) merge(trace *tftree.SpanTree, metadata M) error {
+ obj.metadata = append(obj.metadata, metadata)
+
+ mergeRoot(obj.tree.Root, trace.Root)
+
+ copyVisitor := ©TreeVisitor{to: obj.tree, toParent: obj.tree.Root.SpanID}
+ trace.Visit(copyVisitor)
+ if copyVisitor.err != nil {
+ return copyVisitor.err
+ }
+
+ return nil
+}
+
+func mergeRoot(base *model.Span, tail *model.Span) {
+ mergeRootInterval(base, tail)
+ mergeRootTags(base, tail)
+ mergeRootLogs(base, tail)
+}
+
+func mergeRootInterval(base *model.Span, tail *model.Span) {
+ startTime := base.StartTime
+ if tail.StartTime.Before(startTime) {
+ startTime = tail.StartTime
+ }
+
+ endTime := base.StartTime.Add(base.Duration)
+ tailEndTime := tail.StartTime.Add(tail.Duration)
+ if tailEndTime.After(endTime) {
+ endTime = tailEndTime
+ }
+
+ base.StartTime = startTime
+ base.Duration = endTime.Sub(startTime)
+}
+
+func mergeRootTags(base *model.Span, tail *model.Span) {
+ tagPos := map[string]int{}
+ for pos, tag := range base.Tags {
+ tagPos[tag.Key] = pos
+ }
+
+ for _, tag := range tail.Tags {
+ if pos, hasTag := tagPos[tag.Key]; hasTag {
+ if tail.StartTime.After(base.StartTime) {
+ // the newer value wins
+ base.Tags[pos] = tag
+ }
+ } else {
+ base.Tags = append(base.Tags, tag)
+ }
+ }
+}
+
+func mergeRootLogs(base *model.Span, tail *model.Span) {
+ base.Logs = append(base.Logs, tail.Logs...)
+}
+
+func (obj *object[M]) identifyLinks() {
+ for spanId := range obj.tree.Children(obj.tree.Root.SpanID) {
+ span := obj.tree.Span(spanId)
+ pseudoType, isPseudo := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType)
+ if !(isPseudo && pseudoType.VStr == string(zconstants.PseudoTypeLink)) {
+ continue
+ }
+
+ target, hasTarget := zconstants.LinkedKeyFromSpan(span)
+ if !hasTarget {
+ continue
+ }
+
+ linkRoleTag, hasLinkRole := model.KeyValues(span.Tags).FindByKey(zconstants.LinkRole)
+ if !hasLinkRole {
+ continue
+ }
+ linkRole := linkRoleTag.VStr
+
+ linkClassTag, hasLinkClass := model.KeyValues(span.Tags).FindByKey(zconstants.LinkClass)
+ linkClass := ""
+ if hasLinkClass {
+ linkClass = linkClassTag.VStr
+ }
+
+ obj.links = append(obj.links, TargetLink{
+ Key: target,
+ Role: zconstants.LinkRoleValue(linkRole),
+ Class: linkClass,
+ })
+ }
+}
+
+type TargetLink struct {
+ Key objKey
+ Role zconstants.LinkRoleValue
+ Class string
+}
+
+type copyTreeVisitor struct {
+ to *tftree.SpanTree
+ toParent model.SpanID
+ err error
+}
+
+func (visitor *copyTreeVisitor) Enter(tree *tftree.SpanTree, span *model.Span) tftree.TreeVisitor {
+ if span.SpanID != tree.Root.SpanID {
+ spanCopy, err := tftree.CopySpan(span)
+ if err != nil {
+ visitor.err = err
+ return nil
+ }
+
+ visitor.to.Add(spanCopy, visitor.toParent)
+
+ return ©TreeVisitor{
+ to: visitor.to,
+ toParent: spanCopy.SpanID,
+ }
+ }
+
+ return visitor
+}
+
+func (visitor *copyTreeVisitor) Exit(tree *tftree.SpanTree, span *model.Span) {}
+
+type abLink struct {
+ alpha, beta objKey
+
+ alphaIsParent bool // this needs to be changed if there are link roles other than parent and child
+ class string
+}
+
+func (link abLink) isParent(key objKey) bool {
+ if link.alphaIsParent {
+ return link.alpha == key
+ } else {
+ return link.beta == key
+ }
+}
+
+func abLinkFromTargetLink(subject objKey, link TargetLink) abLink {
+ if groupingKeyLess(subject, link.Key) {
+ return abLink{
+ alpha: subject,
+ beta: link.Key,
+ alphaIsParent: link.Role == zconstants.LinkRoleChild,
+ class: link.Class,
+ }
+ } else {
+ return abLink{
+ beta: subject,
+ alpha: link.Key,
+ alphaIsParent: link.Role != zconstants.LinkRoleChild,
+ class: link.Class,
+ }
+ }
+}
+
+func groupingKeyLess(left, right objKey) bool {
+ if left.Group != right.Group {
+ return left.Group < right.Group
+ }
+
+ if left.Resource != right.Resource {
+ return left.Resource < right.Resource
+ }
+
+ if left.Cluster != right.Cluster {
+ return left.Cluster < right.Cluster
+ }
+
+ if left.Namespace != right.Namespace {
+ return left.Namespace < right.Namespace
+ }
+
+ if left.Name != right.Name {
+ return left.Name < right.Name
+ }
+
+ return false
+}
+
+type abLinkMap map[objKey]map[objKey]abLink
+
+func (m abLinkMap) insert(link abLink) {
+ m.insertDirected(link.alpha, link.beta, link)
+ m.insertDirected(link.beta, link.alpha, link)
+}
+
+func (m abLinkMap) insertDirected(k1, k2 objKey, link abLink) {
+ v1, hasK1 := m[k1]
+ if !hasK1 {
+ v1 = map[objKey]abLink{}
+ m[k1] = v1
+ }
+ v1[k2] = link
+}
+
+func (m abLinkMap) detectRoot(seed objKey, vertexFilter func(objKey) bool) (_root objKey, _hasCycle bool) {
+ visited := sets.New[objKey]()
+ return m.dfsRoot(visited, seed, vertexFilter)
+}
+
+func (m abLinkMap) dfsRoot(visited sets.Set[objKey], key objKey, vertexFilter func(objKey) bool) (_root objKey, _hasCycle bool) {
+ if visited.Has(key) {
+ return key, true
+ }
+ visited.Insert(key) // avoid infinite recursion
+
+ for peer, link := range m[key] {
+ if !vertexFilter(peer) {
+ continue
+ }
+
+ if link.isParent(peer) {
+ return m.dfsRoot(visited, peer, vertexFilter)
+ }
+ }
+
+ return key, false // key has no parent, so key is root
+}
+
+type componentTaint = int
+
+type connectedComponent = []objKey
+
+func (*Merger[M]) findConnectedComponents(objects map[objKey]*object[M], abLinks abLinkMap) []connectedComponent {
+ objectKeys := make(sets.Set[objKey], len(objects))
+ for gk := range objects {
+ objectKeys.Insert(gk)
+ }
+
+ var taintCounter componentTaint
+
+ taints := map[objKey]componentTaint{}
+
+ for {
+ seed, hasMore := peekArbitraryFromSet(objectKeys)
+ if !hasMore {
+ break
+ }
+
+ dfsTaint(objectKeys, abLinks, taints, taintCounter, seed)
+ taintCounter += 1
+ }
+
+ components := make([]connectedComponent, taintCounter)
+ for key, taint := range taints {
+ components[taint] = append(components[taint], key)
+ }
+
+ return components
+}
+
+func peekArbitraryFromSet[T comparable](set sets.Set[T]) (T, bool) {
+ for value := range set {
+ return value, true
+ }
+
+ return reflectutil.ZeroOf[T](), false
+}
+
+func dfsTaint(
+ keys sets.Set[objKey],
+ abLinks abLinkMap,
+ taints map[objKey]componentTaint,
+ taintId componentTaint,
+ seed objKey,
+) {
+ taints[seed] = taintId
+ delete(keys, seed) // delete before diving in to avoid recursing backwards
+
+ for peer := range abLinks[seed] {
+ if _, remaining := keys[peer]; !remaining {
+ continue // this should be unreachable
+ }
+
+ dfsTaint(keys, abLinks, taints, taintId, peer)
+ }
+}
+
+type MergeTree[M any] struct {
+ Metadata []M
+
+ Tree *tftree.SpanTree
+}
+
+func newMergeTree[M any](
+ members []*object[M],
+ abLinks abLinkMap,
+) (*MergeTree[M], error) {
+ metadata := []M{}
+
+ for _, member := range members {
+ metadata = append(metadata, member.metadata...)
+ }
+
+ merged, err := mergeLinkedTraces(members, abLinks)
+ if err != nil {
+ return nil, err
+ }
+
+ return &MergeTree[M]{
+ Metadata: metadata,
+ Tree: merged,
+ }, nil
+}
+
+func mergeLinkedTraces[M any](objects []*object[M], abLinks abLinkMap) (*tftree.SpanTree, error) {
+ trees := make(map[objKey]*object[M], len(objects))
+ for _, obj := range objects {
+ trees[obj.key] = obj
+ }
+
+ rootKey, _ := abLinks.detectRoot(objects[0].key, func(key objKey) bool {
+ _, hasTree := trees[key]
+ return hasTree
+ })
+
+ tree := trees[rootKey].tree
+ treeObjects := sets.New(rootKey)
+
+ pendingObjects := []objKey{rootKey}
+ for len(pendingObjects) > 0 {
+ subj := pendingObjects[len(pendingObjects)-1]
+ pendingObjects = pendingObjects[:len(pendingObjects)-1]
+
+ for _, link := range trees[subj].links {
+ if link.Role != zconstants.LinkRoleChild {
+ continue
+ }
+
+ parentSpan := trees[subj].tree.Root
+ if link.Class != "" {
+ virtualSpan := createVirtualSpan(tree.Root.TraceID, parentSpan, "", link.Class)
+ tree.Add(virtualSpan, parentSpan.SpanID)
+ parentSpan = virtualSpan
+ }
+
+ if treeObjects.Has(link.Key) {
+ parentSpan.Warnings = append(parentSpan.Warnings, fmt.Sprintf("repeated object %v omitted", link.Key))
+ // duplicate
+ continue
+ }
+
+ subtree, hasSubtree := trees[link.Key]
+ if !hasSubtree {
+ // this link was not fetched, e.g. because of fetch limit or link selector
+ continue
+ }
+
+ tree.AddTree(subtree.tree, parentSpan.SpanID)
+ treeObjects.Insert(link.Key)
+ pendingObjects = append(pendingObjects, link.Key)
+ }
+ }
+
+ return tree, nil
+}
+
+func createVirtualSpan(traceId model.TraceID, span *model.Span, opName string, svcName string) *model.Span {
+ spanId := model.SpanID(rand.Uint64())
+
+ return &model.Span{
+ TraceID: traceId,
+ SpanID: spanId,
+ OperationName: opName,
+ Flags: 0,
+ StartTime: span.StartTime,
+ Duration: span.Duration,
+ Tags: []model.KeyValue{
+ {
+ Key: zconstants.PseudoType,
+ VType: model.StringType,
+ VStr: string(zconstants.PseudoTypeLinkClass),
+ },
+ },
+ Process: &model.Process{
+ ServiceName: svcName,
+ },
+ ProcessID: "1",
+ }
+}
diff --git a/pkg/frontend/reader/merge/merge_test.go b/pkg/frontend/reader/merge/merge_test.go
new file mode 100644
index 00000000..f66574b0
--- /dev/null
+++ b/pkg/frontend/reader/merge/merge_test.go
@@ -0,0 +1,318 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package merge_test
+
+import (
+ "context"
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/jaegertracing/jaeger/model"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/kubewharf/kelemetry/pkg/frontend/reader/merge"
+ tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
+ tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
+ utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+ "github.com/kubewharf/kelemetry/pkg/util/zconstants"
+)
+
+func newTrace(id uint64, key utilobject.Key, startTime int64, endTime int64, links []merge.TargetLink) merge.TraceWithMetadata[uint64] {
+ traceId := model.NewTraceID(id, id)
+ objectSpan := &model.Span{
+ TraceID: traceId,
+ SpanID: model.SpanID(id | (1 << 16)),
+ StartTime: time.Time{}.Add(time.Duration(startTime)),
+ Duration: time.Duration(endTime - startTime),
+ Tags: append(
+ mapToTags(zconstants.KeyToSpanTags(key)),
+ model.String(zconstants.TraceSource, zconstants.TraceSourceObject),
+ model.String(zconstants.PseudoType, string(zconstants.PseudoTypeObject)),
+ ),
+ }
+ spans := []*model.Span{objectSpan}
+
+ for i, link := range links {
+ tags := zconstants.KeyToSpanTags(key)
+ zconstants.TagLinkedObject(tags, zconstants.LinkRef{
+ Key: link.Key,
+ Role: link.Role,
+ Class: link.Class,
+ })
+
+ spans = append(spans, &model.Span{
+ TraceID: traceId,
+ SpanID: model.SpanID(id | (3 << 16) | (uint64(i) << 20)),
+ StartTime: objectSpan.StartTime,
+ Duration: objectSpan.Duration,
+ Tags: append(
+ mapToTags(tags),
+ model.String(zconstants.TraceSource, zconstants.TraceSourceObject),
+ model.String(zconstants.PseudoType, string(zconstants.PseudoTypeLink)),
+ ),
+ References: []model.SpanRef{model.NewChildOfRef(traceId, objectSpan.SpanID)},
+ })
+ }
+
+ spans = append(spans, &model.Span{
+ TraceID: traceId,
+ SpanID: model.SpanID(id | (2 << 16)),
+ StartTime: time.Time{}.Add(time.Duration(startTime+endTime) / 2),
+ Duration: time.Duration(endTime-startTime) / 4,
+ Tags: append(
+ mapToTags(zconstants.KeyToSpanTags(key)),
+ model.String(zconstants.TraceSource, zconstants.TraceSourceEvent),
+ model.String(zconstants.NotPseudo, zconstants.NotPseudo),
+ ),
+ References: []model.SpanRef{model.NewChildOfRef(traceId, objectSpan.SpanID)},
+ })
+
+ tree := tftree.NewSpanTree(spans)
+
+ return merge.TraceWithMetadata[uint64]{
+ Tree: tree,
+ Metadata: id,
+ }
+}
+
+func mapToTags(m map[string]string) (out []model.KeyValue) {
+ for key, value := range m {
+ out = append(out, model.String(key, value))
+ }
+
+ return out
+}
+
+type traceList []merge.TraceWithMetadata[uint64]
+
+func (list traceList) append(idLow uint32, key utilobject.Key, links []merge.TargetLink) traceList {
+ for time := 0; time < 4; time++ {
+ id := uint64(idLow) | (uint64(time) << 8)
+ list = append(list, newTrace(
+ id, key,
+ int64(time*10), int64((time+1)*10),
+ links,
+ ))
+ }
+ return list
+}
+
+// IDs:
+// - rs = 0x10, 0x11
+// - dp = 0x20
+// - pod = 0x30 | rs | (replica*2)
+// - node = 0x40
+func sampleTraces(withPods bool, withNode bool) traceList {
+ rsKeys := []utilobject.Key{
+ {
+ Cluster: "test",
+ Group: "apps",
+ Resource: "replicasets",
+ Namespace: "default",
+ Name: "dp-spec1",
+ },
+ {
+ Cluster: "test",
+ Group: "apps",
+ Resource: "replicasets",
+ Namespace: "default",
+ Name: "dp-spec2",
+ },
+ }
+ dpKey := utilobject.Key{
+ Cluster: "test",
+ Group: "apps",
+ Resource: "deployments",
+ Namespace: "default",
+ Name: "dp",
+ }
+ podKeys := [][]utilobject.Key{
+ {
+ {
+ Cluster: "test",
+ Group: "",
+ Resource: "pods",
+ Namespace: "default",
+ Name: "dp-spec1-replica1",
+ },
+ {
+ Cluster: "test",
+ Group: "",
+ Resource: "pods",
+ Namespace: "default",
+ Name: "dp-spec1-replica2",
+ },
+ },
+ {
+ {
+ Cluster: "test",
+ Group: "",
+ Resource: "pods",
+ Namespace: "default",
+ Name: "dp-spec2-replica1",
+ },
+ {
+ Cluster: "test",
+ Group: "",
+ Resource: "pods",
+ Namespace: "default",
+ Name: "dp-spec2-replica2",
+ },
+ },
+ }
+ nodeKey := utilobject.Key{
+ Cluster: "test",
+ Group: "",
+ Resource: "nodes",
+ Namespace: "",
+ Name: "node",
+ }
+
+ list := traceList{}
+ for spec := uint32(0); spec < 2; spec++ {
+ rsLinks := []merge.TargetLink{
+ {Key: dpKey, Role: zconstants.LinkRoleParent, Class: "children"},
+ }
+ if withPods {
+ rsLinks = append(rsLinks,
+ merge.TargetLink{Key: podKeys[spec][0], Role: zconstants.LinkRoleChild, Class: "children"},
+ merge.TargetLink{Key: podKeys[spec][1], Role: zconstants.LinkRoleChild, Class: "children"},
+ )
+ }
+ list = list.append(0x10|spec, rsKeys[spec], rsLinks)
+ }
+ list = list.append(0x20, dpKey, []merge.TargetLink{
+ {Key: rsKeys[0], Role: zconstants.LinkRoleChild, Class: "children"},
+ {Key: rsKeys[1], Role: zconstants.LinkRoleChild, Class: "children"},
+ })
+
+ nodeLinks := []merge.TargetLink{}
+ if withPods {
+ for spec := uint32(0); spec < 2; spec++ {
+ for replica := uint32(0); replica < 2; replica++ {
+ podLinks := []merge.TargetLink{
+ {Key: rsKeys[spec], Role: zconstants.LinkRoleParent, Class: "children"},
+ }
+ if withNode {
+ podLinks = append(podLinks, merge.TargetLink{Key: nodeKey, Role: zconstants.LinkRoleChild, Class: "node"})
+ }
+ list = list.append(0x30|spec|(replica<<1), podKeys[spec][replica], podLinks)
+ nodeLinks = append(nodeLinks, merge.TargetLink{Key: podKeys[spec][replica], Role: zconstants.LinkRoleParent, Class: "node"})
+ }
+ }
+ }
+ if withNode {
+ list = list.append(0x40, nodeKey, nodeLinks)
+ }
+ return list
+}
+
+func do(
+ t *testing.T,
+ clipTimeStart, clipTimeEnd int64,
+ traces traceList,
+ activePrefixLength int,
+ linkSelector tfconfig.LinkSelector,
+ expectGroupSizes []int,
+ expectObjectCounts []int,
+) {
+ t.Helper()
+
+ assert := assert.New(t)
+
+ active := []merge.TraceWithMetadata[uint64]{}
+ for _, trace := range traces[:activePrefixLength] {
+ traceTime := int64(trace.Tree.Root.StartTime.Sub(time.Time{}))
+ if clipTimeStart <= traceTime && traceTime < clipTimeEnd {
+ active = append(active, trace)
+ }
+ }
+
+ merger := merge.Merger[uint64]{}
+ _, err := merger.AddTraces(active)
+ assert.NoError(err)
+
+ assert.NoError(merger.FollowLinks(
+ context.Background(),
+ linkSelector,
+ time.Time{}.Add(time.Duration(clipTimeStart)),
+ time.Time{}.Add(time.Duration(clipTimeEnd)),
+ func(
+ ctx context.Context,
+ key utilobject.Key,
+ startTime, endTime time.Time,
+ limit int,
+ ) (out []merge.TraceWithMetadata[uint64], _ error) {
+ for _, trace := range traces {
+ traceKey := zconstants.ObjectKeyFromSpan(trace.Tree.Root)
+ traceTime := int64(trace.Tree.Root.StartTime.Sub(time.Time{}))
+ if key == traceKey && clipTimeStart <= traceTime && traceTime < clipTimeEnd {
+ out = append(out, trace)
+ }
+ }
+
+ return out, nil
+ },
+ len(traces),
+ int32(len(traces)),
+ false,
+ ))
+
+ result, err := merger.MergeTraces()
+ assert.NoError(err)
+ assert.Len(result, len(expectGroupSizes))
+
+ sort.Ints(expectGroupSizes)
+ actualGroupSizes := make([]int, len(result))
+ for i, group := range result {
+ actualGroupSizes[i] = len(group.Metadata)
+ }
+ sort.Ints(actualGroupSizes)
+ assert.Equal(expectGroupSizes, actualGroupSizes)
+
+ actualObjectCounts := []int{}
+ for _, group := range result {
+ objectCount := 0
+ for _, span := range group.Tree.GetSpans() {
+ pseudoTag, isPseudo := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType)
+ if isPseudo && pseudoTag.VStr == string(zconstants.PseudoTypeObject) {
+ objectCount += 1
+ }
+ }
+
+ actualObjectCounts = append(actualObjectCounts, objectCount)
+ }
+ sort.Ints(actualObjectCounts)
+ assert.Equal(expectObjectCounts, actualObjectCounts)
+}
+
+func TestFullTree(t *testing.T) {
+ do(t, 10, 30, sampleTraces(true, true), 4, tfconfig.ConstantLinkSelector(true), []int{2 * (1 + 2 + 4 + 1)}, []int{1 + 2 + 4 + 1})
+}
+
+func TestFilteredTree(t *testing.T) {
+ do(t, 10, 30, sampleTraces(true, true), 4, rsPodLinksOnly{}, []int{2 * (1 + 2)}, []int{1 + 2})
+}
+
+type rsPodLinksOnly struct{}
+
+func (rsPodLinksOnly) Admit(parent, child utilobject.Key, parentIsSource bool, class string) tfconfig.LinkSelector {
+ if parent.Resource == "replicasets" && child.Resource == "pods" {
+ return rsPodLinksOnly{}
+ } else {
+ return nil
+ }
+}
diff --git a/pkg/frontend/reader/reader.go b/pkg/frontend/reader/reader.go
index 08b86ebf..2f611e40 100644
--- a/pkg/frontend/reader/reader.go
+++ b/pkg/frontend/reader/reader.go
@@ -29,11 +29,14 @@ import (
jaegerbackend "github.com/kubewharf/kelemetry/pkg/frontend/backend"
"github.com/kubewharf/kelemetry/pkg/frontend/clusterlist"
+ "github.com/kubewharf/kelemetry/pkg/frontend/reader/merge"
transform "github.com/kubewharf/kelemetry/pkg/frontend/tf"
tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
+ tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
"github.com/kubewharf/kelemetry/pkg/frontend/tracecache"
"github.com/kubewharf/kelemetry/pkg/manager"
utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+ reflectutil "github.com/kubewharf/kelemetry/pkg/util/reflect"
"github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
@@ -46,7 +49,10 @@ type Interface interface {
}
type options struct {
- cacheExtensions bool
+ cacheExtensions bool
+ followLinkConcurrency int
+ followLinkLimit int32
+ followLinksInList bool
}
func (options *options) Setup(fs *pflag.FlagSet) {
@@ -56,6 +62,24 @@ func (options *options) Setup(fs *pflag.FlagSet) {
false,
"cache extension trace search result, otherwise trace is searched again every time result is reloaded",
)
+ fs.IntVar(
+ &options.followLinkConcurrency,
+ "frontend-follow-link-concurrency",
+ 20,
+ "number of concurrent trace per request to follow links",
+ )
+ fs.Int32Var(
+ &options.followLinkLimit,
+ "frontend-follow-link-limit",
+ 10,
+ "maximum number of linked objects to follow per search result",
+ )
+ fs.BoolVar(
+ &options.followLinksInList,
+ "frontend-follow-links-in-list",
+ true,
+ "whether links should be recursed into when listing traces",
+ )
}
func (options *options) EnableFlag() *bool { return nil }
@@ -127,78 +151,73 @@ func (reader *spanReader) FindTraces(ctx context.Context, query *spanstore.Trace
}
reader.Logger.WithField("query", query).
- WithField("exclusive", config.UseSubtree).
WithField("config", config.Name).
Debug("start trace list")
- thumbnails, err := reader.Backend.List(ctx, query, config.UseSubtree)
+
+ if len(query.OperationName) > 0 {
+ if query.Tags == nil {
+ query.Tags = map[string]string{}
+ }
+ query.Tags["cluster"] = query.OperationName
+ }
+
+ tts, err := reader.Backend.List(ctx, query)
if err != nil {
return nil, err
}
+ twmList := make([]merge.TraceWithMetadata[any], len(tts))
+ for i, tt := range tts {
+ twmList[i] = merge.TraceWithMetadata[any]{
+ Tree: tt.Spans,
+ Metadata: tt.Identifier,
+ }
+ }
+
+ merger := merge.Merger[any]{}
+ if _, err := merger.AddTraces(twmList); err != nil {
+ return nil, fmt.Errorf("group traces by object: %w", err)
+ }
+
+ if reader.options.followLinksInList {
+ if err := merger.FollowLinks(
+ ctx,
+ config.LinkSelector,
+ query.StartTimeMin, query.StartTimeMax,
+ mergeListWithBackend[any](reader.Backend, reflectutil.Identity[any], OriginalTraceRequest{FindTraces: query}),
+ reader.options.followLinkConcurrency, reader.options.followLinkLimit, false,
+ ); err != nil {
+ return nil, fmt.Errorf("follow links: %w", err)
+ }
+ }
+
+ mergeTrees, err := merger.MergeTraces()
+ if err != nil {
+ return nil, fmt.Errorf("merging split and linked traces: %w", err)
+ }
+
var rootKey *utilobject.Key
if rootKeyValue, ok := utilobject.FromMap(query.Tags); ok {
rootKey = &rootKeyValue
}
- mergedEntries := mergeSegments(thumbnails)
-
cacheEntries := []tracecache.Entry{}
traces := []*model.Trace{}
- for _, entry := range mergedEntries {
+ for _, mergeTree := range mergeTrees {
cacheId := generateCacheId(config.Id)
- for _, span := range entry.spans {
- span.TraceID = cacheId
- for i := range span.References {
- span.References[i].TraceID = cacheId
- }
- }
-
- entry.spans = filterTimeRange(entry.spans, query.StartTimeMin, query.StartTimeMax)
-
- trace := &model.Trace{
- ProcessMap: []model.Trace_ProcessMapping{{
- ProcessID: "0",
- Process: model.Process{},
- }},
- Spans: entry.spans,
+ trace, extensionCache, err := reader.prepareEntry(ctx, rootKey, query, mergeTree.Tree, cacheId)
+ if err != nil {
+ return nil, err
}
- displayMode := extractDisplayMode(cacheId)
-
- extensions := &transform.FetchExtensionsAndStoreCache{}
-
- if err := reader.Transformer.Transform(
- ctx, trace, rootKey, displayMode,
- extensions,
- query.StartTimeMin, query.StartTimeMax,
- ); err != nil {
- return nil, fmt.Errorf("trace transformation failed: %w", err)
- }
traces = append(traces, trace)
- identifiers := make([]json.RawMessage, len(entry.identifiers))
- for i, identifier := range entry.identifiers {
- idJson, err := json.Marshal(identifier)
- if err != nil {
- return nil, fmt.Errorf("thumbnail identifier marshal: %w", err)
- }
-
- identifiers[i] = json.RawMessage(idJson)
+ cacheEntry, err := reader.prepareCache(rootKey, query, mergeTree.Metadata, cacheId, extensionCache)
+ if err != nil {
+ return nil, err
}
- cacheEntry := tracecache.Entry{
- LowId: cacheId.Low,
- Value: tracecache.EntryValue{
- Identifiers: identifiers,
- StartTime: query.StartTimeMin,
- EndTime: query.StartTimeMax,
- RootObject: rootKey,
- },
- }
- if reader.options.cacheExtensions {
- cacheEntry.Value.Extensions = extensions.Cache
- }
cacheEntries = append(cacheEntries, cacheEntry)
}
@@ -213,6 +232,80 @@ func (reader *spanReader) FindTraces(ctx context.Context, query *spanstore.Trace
return traces, nil
}
+func (reader *spanReader) prepareEntry(
+ ctx context.Context,
+ rootKey *utilobject.Key,
+ query *spanstore.TraceQueryParameters,
+ tree *tftree.SpanTree,
+ cacheId model.TraceID,
+) (*model.Trace, []tracecache.ExtensionCache, error) {
+ spans := tree.GetSpans()
+
+ for _, span := range spans {
+ span.TraceID = cacheId
+ for i := range span.References {
+ span.References[i].TraceID = cacheId
+ }
+ }
+
+ spans = filterTimeRange(spans, query.StartTimeMin, query.StartTimeMax)
+
+ trace := &model.Trace{
+ ProcessMap: []model.Trace_ProcessMapping{{
+ ProcessID: "0",
+ Process: model.Process{},
+ }},
+ Spans: spans,
+ }
+
+ displayMode := extractDisplayMode(cacheId)
+
+ extensions := &transform.FetchExtensionsAndStoreCache{}
+
+ if err := reader.Transformer.Transform(
+ ctx, trace, rootKey, displayMode,
+ extensions,
+ query.StartTimeMin, query.StartTimeMax,
+ ); err != nil {
+ return nil, nil, fmt.Errorf("trace transformation failed: %w", err)
+ }
+
+ return trace, extensions.Cache, nil
+}
+
+func (reader *spanReader) prepareCache(
+ rootKey *utilobject.Key,
+ query *spanstore.TraceQueryParameters,
+ identifiers []any,
+ cacheId model.TraceID,
+ extensionCache []tracecache.ExtensionCache,
+) (tracecache.Entry, error) {
+ identifiersJson := make([]json.RawMessage, len(identifiers))
+ for i, identifier := range identifiers {
+ idJson, err := json.Marshal(identifier)
+ if err != nil {
+ return tracecache.Entry{}, fmt.Errorf("thumbnail identifier marshal: %w", err)
+ }
+
+ identifiersJson[i] = json.RawMessage(idJson)
+ }
+
+ cacheEntry := tracecache.Entry{
+ LowId: cacheId.Low,
+ Value: tracecache.EntryValue{
+ Identifiers: identifiersJson,
+ StartTime: query.StartTimeMin,
+ EndTime: query.StartTimeMax,
+ RootObject: rootKey,
+ },
+ }
+ if reader.options.cacheExtensions {
+ cacheEntry.Value.Extensions = extensionCache
+ }
+
+ return cacheEntry, nil
+}
+
func (reader *spanReader) GetTrace(ctx context.Context, cacheId model.TraceID) (*model.Trace, error) {
entry, err := reader.TraceCache.Fetch(ctx, cacheId.Low)
if err != nil {
@@ -222,13 +315,9 @@ func (reader *spanReader) GetTrace(ctx context.Context, cacheId model.TraceID) (
return nil, fmt.Errorf("trace %v not found", cacheId)
}
- aggTrace := &model.Trace{
- ProcessMap: []model.Trace_ProcessMapping{{
- ProcessID: "0",
- Process: model.Process{},
- }},
- }
+ displayMode := extractDisplayMode(cacheId)
+ traces := make([]merge.TraceWithMetadata[struct{}], 0, len(entry.Identifiers))
for _, identifier := range entry.Identifiers {
trace, err := reader.Backend.Get(ctx, identifier, cacheId, entry.StartTime, entry.EndTime)
if err != nil {
@@ -236,7 +325,43 @@ func (reader *spanReader) GetTrace(ctx context.Context, cacheId model.TraceID) (
}
clipped := filterTimeRange(trace.Spans, entry.StartTime, entry.EndTime)
- aggTrace.Spans = append(aggTrace.Spans, clipped...)
+ traces = append(traces, merge.TraceWithMetadata[struct{}]{Tree: tftree.NewSpanTree(clipped)})
+ }
+
+ merger := merge.Merger[struct{}]{}
+ if _, err := merger.AddTraces(traces); err != nil {
+ return nil, fmt.Errorf("grouping traces by object: %w", err)
+ }
+
+ displayConfig := reader.TransformConfigs.GetById(displayMode)
+ if displayConfig == nil {
+ return nil, fmt.Errorf("display mode %x does not exist", displayMode)
+ }
+
+ if err := merger.FollowLinks(
+ ctx,
+ displayConfig.LinkSelector,
+ entry.StartTime, entry.EndTime,
+ mergeListWithBackend[struct{}](reader.Backend, func(any) struct{} { return struct{}{} }, OriginalTraceRequest{GetTrace: &cacheId}),
+ reader.options.followLinkConcurrency, reader.options.followLinkLimit, true,
+ ); err != nil {
+ return nil, fmt.Errorf("cannot follow links: %w", err)
+ }
+ mergedTrees, err := merger.MergeTraces()
+ if err != nil {
+ return nil, fmt.Errorf("merging linked trees: %w", err)
+ }
+
+ // if spans were connected, they should continue to be connected since link spans cannot be deleted, so assume there is only one trace
+ if len(mergedTrees) != 1 {
+ return nil, fmt.Errorf("inconsistent linked trace count %d", len(mergedTrees))
+ }
+ mergedTree := mergedTrees[0]
+ aggTrace := &model.Trace{
+ ProcessMap: []model.Trace_ProcessMapping{{
+ ProcessID: "0",
+ }},
+ Spans: mergedTree.Tree.GetSpans(),
}
var extensions transform.ExtensionProcessor = &transform.FetchExtensionsAndStoreCache{}
@@ -244,7 +369,6 @@ func (reader *spanReader) GetTrace(ctx context.Context, cacheId model.TraceID) (
extensions = &transform.LoadExtensionCache{Cache: entry.Extensions}
}
- displayMode := extractDisplayMode(cacheId)
if err := reader.Transformer.Transform(
ctx, aggTrace, entry.RootObject, displayMode,
extensions,
@@ -299,3 +423,45 @@ func filterTimeRange(spans []*model.Span, startTime, endTime time.Time) []*model
return retained
}
+
+type (
+ OriginalTraceRequestKey struct{}
+ OriginalTraceRequest struct {
+ GetTrace *model.TraceID
+ FindTraces *spanstore.TraceQueryParameters
+ }
+)
+
+func mergeListWithBackend[M any](backend jaegerbackend.Backend, convertMetadata func(any) M, otr OriginalTraceRequest) merge.ListFunc[M] {
+ return func(
+ ctx context.Context,
+ key utilobject.Key,
+ startTime time.Time, endTime time.Time,
+ limit int,
+ ) ([]merge.TraceWithMetadata[M], error) {
+ tags := zconstants.KeyToSpanTags(key)
+
+ tts, err := backend.List(
+ context.WithValue(ctx, OriginalTraceRequestKey{}, otr),
+ &spanstore.TraceQueryParameters{
+ Tags: tags,
+ StartTimeMin: startTime,
+ StartTimeMax: endTime,
+ NumTraces: limit,
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ twmList := make([]merge.TraceWithMetadata[M], len(tts))
+ for i, tt := range tts {
+ twmList[i] = merge.TraceWithMetadata[M]{
+ Tree: tt.Spans,
+ Metadata: convertMetadata(tt.Identifier),
+ }
+ }
+
+ return twmList, nil
+ }
+}
diff --git a/pkg/frontend/tf/config/config.go b/pkg/frontend/tf/config/config.go
index b5bd4849..57524228 100644
--- a/pkg/frontend/tf/config/config.go
+++ b/pkg/frontend/tf/config/config.go
@@ -15,7 +15,12 @@
package tfconfig
import (
+ "fmt"
+ "sort"
"strconv"
+ "strings"
+
+ "k8s.io/apimachinery/pkg/util/sets"
"github.com/kubewharf/kelemetry/pkg/frontend/extension"
"github.com/kubewharf/kelemetry/pkg/manager"
@@ -51,24 +56,43 @@ type Config struct {
Id Id
// The config name, used in search page display.
Name string
- // If true, only displays the spans below the matched span.
- // If false, displays the whole trace including parent and sibling spans.
- UseSubtree bool
+ // Base config name without modifiers, used to help reconstruct the name.
+ BaseName string
+ // Names of modifiers, used to help reconstruct the name.
+ ModifierNames sets.Set[string]
+ // Only links with roles in this set are followed.
+ LinkSelector LinkSelector
// The extension traces for this config.
Extensions []extension.Provider
// The steps to transform the tree
Steps []Step
}
+func (config *Config) RecomputeName() {
+ modifiers := config.ModifierNames.UnsortedList()
+ sort.Strings(modifiers)
+ if len(modifiers) > 0 {
+ config.Name = fmt.Sprintf("%s [%s]", config.BaseName, strings.Join(modifiers, "+"))
+ } else {
+ config.Name = config.BaseName
+ }
+}
+
func (config *Config) Clone() *Config {
steps := make([]Step, len(config.Steps))
copy(steps, config.Steps) // no need to deep clone each step
+ extensions := make([]extension.Provider, len(config.Extensions))
+ copy(extensions, config.Extensions)
+
return &Config{
- Id: config.Id,
- Name: config.Name,
- UseSubtree: config.UseSubtree,
- Steps: steps,
+ Id: config.Id,
+ Name: config.Name,
+ BaseName: config.BaseName,
+ ModifierNames: config.ModifierNames.Clone(),
+ LinkSelector: config.LinkSelector, // modifier changes LinkSelector by wrapping the previous value
+ Extensions: extensions,
+ Steps: steps,
}
}
diff --git a/pkg/frontend/tf/config/file/file.go b/pkg/frontend/tf/config/file/file.go
index af77b9c9..c444cc7e 100644
--- a/pkg/frontend/tf/config/file/file.go
+++ b/pkg/frontend/tf/config/file/file.go
@@ -115,10 +115,9 @@ func (p *FileProvider) loadJsonBytes(jsonBytes []byte) error {
Modifiers map[tfconfig.Id]modifierConfig `json:"modifiers"`
Batches []Batch `json:"batches"`
Configs []struct {
- Id tfconfig.Id `json:"id"`
- Name string `json:"name"`
- UseSubtree bool `json:"useSubtree"`
- Steps json.RawMessage `json:"steps"`
+ Id tfconfig.Id `json:"id"`
+ Name string `json:"name"`
+ Steps json.RawMessage `json:"steps"`
} `json:"configs"`
DefaultConfig tfconfig.Id `json:"defaultConfig"`
}
@@ -157,7 +156,8 @@ func (p *FileProvider) loadJsonBytes(jsonBytes []byte) error {
priority: modifierConfig.Priority,
fn: func(config *tfconfig.Config) {
config.Id |= bitmask
- config.Name += fmt.Sprintf(" [%s]", displayName)
+ config.ModifierNames.Insert(displayName)
+ config.RecomputeName()
modifier.Modify(config)
},
})
@@ -183,10 +183,12 @@ func (p *FileProvider) loadJsonBytes(jsonBytes []byte) error {
}
config := &tfconfig.Config{
- Id: raw.Id,
- Name: raw.Name,
- UseSubtree: raw.UseSubtree,
- Steps: steps,
+ Id: raw.Id,
+ Name: raw.Name,
+ BaseName: raw.Name,
+ ModifierNames: sets.New[string](),
+ LinkSelector: tfconfig.ConstantLinkSelector(false),
+ Steps: steps,
}
p.register(registeredConfig{config: config, modifierClasses: sets.New[string]()})
diff --git a/pkg/frontend/tf/config/link_selector.go b/pkg/frontend/tf/config/link_selector.go
new file mode 100644
index 00000000..d61b0277
--- /dev/null
+++ b/pkg/frontend/tf/config/link_selector.go
@@ -0,0 +1,87 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tfconfig
+
+import utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+
+type LinkSelector interface {
+ // Whether to follow the given link.
+ //
+ // If link should be followed, return a non-nil LinkSelector.
+ // The returned object will be used to recursively follow links in the linked object.
+ Admit(parent utilobject.Key, child utilobject.Key, parentIsSource bool, linkClass string) LinkSelector
+}
+
+type ConstantLinkSelector bool
+
+func (selector ConstantLinkSelector) Admit(
+ parent utilobject.Key,
+ child utilobject.Key,
+ parentIsSource bool,
+ linkClass string,
+) LinkSelector {
+ if selector {
+ return selector
+ }
+
+ return nil
+}
+
+type IntersectLinkSelector []LinkSelector
+
+func (selector IntersectLinkSelector) Admit(
+ parentKey utilobject.Key,
+ childKey utilobject.Key,
+ parentIsSource bool,
+ linkClass string,
+) LinkSelector {
+ newChildren := make([]LinkSelector, len(selector))
+
+ for i, child := range selector {
+ newChildren[i] = child.Admit(parentKey, childKey, parentIsSource, linkClass)
+ if newChildren[i] == nil {
+ return nil
+ }
+ }
+
+ return IntersectLinkSelector(newChildren)
+}
+
+type UnionLinkSelector []LinkSelector
+
+func (selector UnionLinkSelector) Admit(
+ parentKey utilobject.Key,
+ childKey utilobject.Key,
+ parentIsSource bool,
+ linkClass string,
+) LinkSelector {
+ newChildren := make([]LinkSelector, len(selector))
+
+ ok := false
+ for i, child := range selector {
+ if child != nil {
+ newChildren[i] = child.Admit(parentKey, childKey, parentIsSource, linkClass)
+ if newChildren[i] != nil {
+ ok = true
+ }
+ }
+ }
+
+ if ok {
+ return UnionLinkSelector(newChildren)
+ }
+
+ return nil
+}
diff --git a/pkg/frontend/tf/defaults/modifier/exclusive.go b/pkg/frontend/tf/defaults/modifier/exclusive.go
deleted file mode 100644
index 7b141675..00000000
--- a/pkg/frontend/tf/defaults/modifier/exclusive.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2023 The Kelemetry Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tfmodifier
-
-import (
- "context"
-
- "github.com/spf13/pflag"
-
- tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
- "github.com/kubewharf/kelemetry/pkg/manager"
-)
-
-func init() {
- manager.Global.ProvideListImpl(
- "tf-modifier/exclusive",
- manager.Ptr(&ExclusiveModifierFactory{}),
- &manager.List[tfconfig.ModifierFactory]{},
- )
-}
-
-type ExclusiveModifierOptions struct {
- enable bool
-}
-
-func (options *ExclusiveModifierOptions) Setup(fs *pflag.FlagSet) {
- fs.BoolVar(&options.enable, "jaeger-tf-exclusive-modifier-enable", true, "enable exclusive modifier and list it in frontend")
-}
-
-func (options *ExclusiveModifierOptions) EnableFlag() *bool { return &options.enable }
-
-type ExclusiveModifierFactory struct {
- options ExclusiveModifierOptions
-}
-
-var _ manager.Component = &ExclusiveModifierFactory{}
-
-func (m *ExclusiveModifierFactory) Options() manager.Options { return &m.options }
-func (m *ExclusiveModifierFactory) Init() error { return nil }
-func (m *ExclusiveModifierFactory) Start(ctx context.Context) error { return nil }
-func (m *ExclusiveModifierFactory) Close(ctx context.Context) error { return nil }
-
-func (*ExclusiveModifierFactory) ListIndex() string { return "exclusive" }
-
-func (*ExclusiveModifierFactory) Build(jsonBuf []byte) (tfconfig.Modifier, error) {
- return &ExclusiveModifier{}, nil
-}
-
-type ExclusiveModifier struct{}
-
-func (*ExclusiveModifier) ModifierClass() string { return "kelemetry.kubewharf.io/exclusive" }
-
-func (*ExclusiveModifier) Modify(config *tfconfig.Config) {
- config.UseSubtree = true
-}
diff --git a/pkg/frontend/tf/defaults/modifier/link_selector.go b/pkg/frontend/tf/defaults/modifier/link_selector.go
new file mode 100644
index 00000000..5d49f590
--- /dev/null
+++ b/pkg/frontend/tf/defaults/modifier/link_selector.go
@@ -0,0 +1,200 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tfmodifier
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/spf13/pflag"
+
+ tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
+ "github.com/kubewharf/kelemetry/pkg/manager"
+ utilmarshal "github.com/kubewharf/kelemetry/pkg/util/marshal"
+ utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+)
+
+func init() {
+ manager.Global.ProvideListImpl(
+ "tf-modifier/link-selector",
+ manager.Ptr(&LinkSelectorModifierFactory{}),
+ &manager.List[tfconfig.ModifierFactory]{},
+ )
+}
+
+type LinkSelectorModifierOptions struct {
+ enable bool
+}
+
+func (options *LinkSelectorModifierOptions) Setup(fs *pflag.FlagSet) {
+ fs.BoolVar(&options.enable, "jaeger-tf-link-selector-modifier-enable", true, "enable link selector modifiers and list it in frontend")
+}
+
+func (options *LinkSelectorModifierOptions) EnableFlag() *bool { return &options.enable }
+
+type LinkSelectorModifierFactory struct {
+ options LinkSelectorModifierOptions
+}
+
+var _ manager.Component = &LinkSelectorModifierFactory{}
+
+func (m *LinkSelectorModifierFactory) Options() manager.Options { return &m.options }
+func (m *LinkSelectorModifierFactory) Init() error { return nil }
+func (m *LinkSelectorModifierFactory) Start(ctx context.Context) error { return nil }
+func (m *LinkSelectorModifierFactory) Close(ctx context.Context) error { return nil }
+
+func (*LinkSelectorModifierFactory) ListIndex() string { return "link-selector" }
+
+func (*LinkSelectorModifierFactory) Build(jsonBuf []byte) (tfconfig.Modifier, error) {
+ modifier := &LinkSelectorModifier{}
+
+ if err := json.Unmarshal(jsonBuf, &modifier); err != nil {
+ return nil, fmt.Errorf("parse link selector modifier config: %w", err)
+ }
+
+ return modifier, nil
+}
+
+type LinkSelectorModifier struct {
+ Class string `json:"modifierClass"`
+ IncludeSiblings bool `json:"includeSiblings"`
+ PatternFilters []LinkPattern `json:"ifAll"`
+ UpwardDistance utilmarshal.Optional[uint32] `json:"upwardDistance"`
+ DownwardDistance utilmarshal.Optional[uint32] `json:"downwardDistance"`
+}
+
+type LinkPattern struct {
+ Parent utilmarshal.ObjectFilter `json:"parent"`
+ Child utilmarshal.ObjectFilter `json:"child"`
+ IncludeFromParent utilmarshal.Optional[bool] `json:"fromParent"`
+ IncludeFromChild utilmarshal.Optional[bool] `json:"fromChild"`
+ LinkClass utilmarshal.Optional[utilmarshal.StringFilter] `json:"linkClass"`
+}
+
+func (pattern *LinkPattern) Matches(parent utilobject.Key, child utilobject.Key, isFromParent bool, linkClass string) bool {
+ if !pattern.Parent.Matches(parent) {
+ return false
+ }
+
+ if !pattern.Child.Matches(child) {
+ return false
+ }
+
+ if !pattern.IncludeFromParent.GetOr(true) && isFromParent {
+ return false
+ }
+
+ if !pattern.IncludeFromChild.GetOr(true) && !isFromParent {
+ return false
+ }
+
+ if pattern.LinkClass.IsSet && !pattern.LinkClass.Value.Matches(linkClass) {
+ return false
+ }
+
+ return true
+}
+
+func (modifier *LinkSelectorModifier) ModifierClass() string {
+ return fmt.Sprintf("kelemetry.kubewharf.io/link-selectors/%s", modifier.Class)
+}
+
+func (modifier *LinkSelectorModifier) Modify(config *tfconfig.Config) {
+ intersectSelector := tfconfig.IntersectLinkSelector{
+ patternLinkSelector{patterns: modifier.PatternFilters},
+ }
+ if !modifier.IncludeSiblings {
+ intersectSelector = append(intersectSelector, denySiblingsLinkSelector{})
+ }
+ if modifier.UpwardDistance.IsSet {
+ intersectSelector = append(
+ intersectSelector,
+ directedDistanceLinkSelector{distance: modifier.UpwardDistance.Value, direction: directionUpwards},
+ )
+ }
+ if modifier.DownwardDistance.IsSet {
+ intersectSelector = append(
+ intersectSelector,
+ directedDistanceLinkSelector{distance: modifier.DownwardDistance.Value, direction: directionDownwards},
+ )
+ }
+
+ config.LinkSelector = tfconfig.UnionLinkSelector{config.LinkSelector, intersectSelector}
+}
+
+type denySiblingsLinkSelector struct {
+ hasFirst bool
+ firstIsFromParent bool
+}
+
+func (s denySiblingsLinkSelector) Admit(
+ parent utilobject.Key,
+ child utilobject.Key,
+ isFromParent bool,
+ linkClass string,
+) tfconfig.LinkSelector {
+ if !s.hasFirst {
+ return denySiblingsLinkSelector{hasFirst: true, firstIsFromParent: isFromParent}
+ }
+ if s.firstIsFromParent != isFromParent {
+ return nil
+ }
+ return s
+}
+
+type patternLinkSelector struct {
+ patterns []LinkPattern
+}
+
+func (s patternLinkSelector) Admit(parent utilobject.Key, child utilobject.Key, isFromParent bool, linkClass string) tfconfig.LinkSelector {
+ for _, pattern := range s.patterns {
+ if !pattern.Matches(parent, child, isFromParent, linkClass) {
+ return nil
+ }
+ }
+
+ return s
+}
+
+type direction bool
+
+const (
+ directionUpwards direction = true
+ directionDownwards direction = false
+)
+
+type directedDistanceLinkSelector struct {
+ direction direction
+ distance uint32
+}
+
+func (d directedDistanceLinkSelector) Admit(
+ parent utilobject.Key,
+ child utilobject.Key,
+ isFromParent bool,
+ linkClass string,
+) tfconfig.LinkSelector {
+ if isFromParent != (d.direction == directionDownwards) {
+ return d
+ }
+ if d.distance == 0 {
+ return nil
+ }
+ return directedDistanceLinkSelector{
+ direction: d.direction,
+ distance: d.distance - 1,
+ }
+}
diff --git a/pkg/frontend/tf/defaults/step/collapse_nesting.go b/pkg/frontend/tf/defaults/step/collapse_nesting.go
index 9d6bfb46..e1725793 100644
--- a/pkg/frontend/tf/defaults/step/collapse_nesting.go
+++ b/pkg/frontend/tf/defaults/step/collapse_nesting.go
@@ -24,6 +24,7 @@ import (
tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
"github.com/kubewharf/kelemetry/pkg/manager"
+ utilmarshal "github.com/kubewharf/kelemetry/pkg/util/marshal"
"github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
@@ -35,13 +36,13 @@ func init() {
)
}
-// Deletes child spans with a traceSource and injects them as logs in the nesting span.
+// Deletes child spans with a non-pseudo trace source and injects them as logs in the nesting span.
//
// Multiple logs of the same span are aggregated into one log, flattening them into a field.
//
// Must be followed by PruneTagsVisitor in the last step.
type CollapseNestingVisitor struct {
- ShouldCollapse StringFilter `json:"shouldCollapse"` // tests traceSource
+ ShouldCollapse utilmarshal.StringFilter `json:"shouldCollapse"` // tests traceSource
TagMappings map[string][]TagMapping `json:"tagMappings"` // key = traceSource
AuditDiffClasses AuditDiffClassification `json:"auditDiffClasses"` // key = prefix
LogTypeMapping map[zconstants.LogType]string `json:"logTypeMapping"` // key = log type, value = log field
@@ -104,7 +105,7 @@ func (classes *AuditDiffClassification) Get(prefix string) *AuditDiffClass {
}
func (visitor CollapseNestingVisitor) Enter(tree *tftree.SpanTree, span *model.Span) tftree.TreeVisitor {
- if _, hasTag := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel); !hasTag {
+ if _, isPseudo := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType); !isPseudo {
return visitor
}
@@ -119,15 +120,16 @@ func (visitor CollapseNestingVisitor) Exit(tree *tftree.SpanTree, span *model.Sp
func (visitor CollapseNestingVisitor) processChild(tree *tftree.SpanTree, span *model.Span, childId model.SpanID) {
childSpan := tree.Span(childId)
- if _, childHasTag := model.KeyValues(childSpan.Tags).FindByKey(zconstants.NestLevel); childHasTag {
+ if _, childIsPseudo := model.KeyValues(childSpan.Tags).FindByKey(zconstants.PseudoType); childIsPseudo {
return
}
+
traceSourceKv, hasTraceSource := model.KeyValues(childSpan.Tags).FindByKey(zconstants.TraceSource)
if !hasTraceSource {
return
}
traceSource := traceSourceKv.VStr
- if !visitor.ShouldCollapse.Test(traceSource) {
+ if !visitor.ShouldCollapse.Matches(traceSource) {
return
}
diff --git a/pkg/frontend/tf/defaults/step/compact_duration.go b/pkg/frontend/tf/defaults/step/compact_duration.go
index 129cd7ed..4aa6b800 100644
--- a/pkg/frontend/tf/defaults/step/compact_duration.go
+++ b/pkg/frontend/tf/defaults/step/compact_duration.go
@@ -45,7 +45,7 @@ func (visitor CompactDurationVisitor) Enter(tree *tftree.SpanTree, span *model.S
func (visitor CompactDurationVisitor) Exit(tree *tftree.SpanTree, span *model.Span) {
// use exit hook to use compact results of children
- if _, hasNestLevel := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel); !hasNestLevel {
+ if _, isPseudo := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType); !isPseudo {
return
}
diff --git a/pkg/frontend/tf/defaults/step/extract_nesting.go b/pkg/frontend/tf/defaults/step/extract_nesting.go
index 533e9297..0d4e3b69 100644
--- a/pkg/frontend/tf/defaults/step/extract_nesting.go
+++ b/pkg/frontend/tf/defaults/step/extract_nesting.go
@@ -20,6 +20,7 @@ import (
tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
"github.com/kubewharf/kelemetry/pkg/manager"
+ utilmarshal "github.com/kubewharf/kelemetry/pkg/util/marshal"
"github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
@@ -31,11 +32,10 @@ func init() {
)
}
-// Deletes spans matching MatchesNestLevel and brings their children one level up.
+// Deletes spans matching MatchesPseudoType and brings their children one level up.
type ExtractNestingVisitor struct {
- // NestLevels returns true if the span should be deleted.
- // It is only called on spans with the tag zconstants.Nesting
- MatchesNestLevel StringFilter `json:"matchesNestLevel"`
+ // Filters the trace sources to delete.
+ MatchesPseudoType utilmarshal.StringFilter `json:"matchesPseudoType"`
}
func (ExtractNestingVisitor) Kind() string { return "ExtractNestingVisitor" }
@@ -46,8 +46,8 @@ func (visitor ExtractNestingVisitor) Enter(tree *tftree.SpanTree, span *model.Sp
return visitor
}
- if nestLevel, ok := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel); ok {
- if visitor.MatchesNestLevel.Test(nestLevel.AsString()) {
+ if pseudoType, ok := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType); ok {
+ if visitor.MatchesPseudoType.Matches(pseudoType.AsString()) {
childrenMap := tree.Children(span.SpanID)
childrenCopy := make([]model.SpanID, 0, len(childrenMap))
for childId := range childrenMap {
diff --git a/pkg/frontend/tf/defaults/step/group_by_trace_source.go b/pkg/frontend/tf/defaults/step/group_by_trace_source.go
index b376495e..b341d3a8 100644
--- a/pkg/frontend/tf/defaults/step/group_by_trace_source.go
+++ b/pkg/frontend/tf/defaults/step/group_by_trace_source.go
@@ -22,6 +22,7 @@ import (
tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
tftree "github.com/kubewharf/kelemetry/pkg/frontend/tf/tree"
"github.com/kubewharf/kelemetry/pkg/manager"
+ utilmarshal "github.com/kubewharf/kelemetry/pkg/util/marshal"
"github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
@@ -33,18 +34,18 @@ func init() {
)
}
-const pseudoSpanNestLevel = "groupByTraceSource"
+const myPseudoType = "groupByTraceSource"
// Splits span logs into pseudospans grouped by traceSource.
type GroupByTraceSourceVisitor struct {
- ShouldBeGrouped StringFilter `json:"shouldBeGrouped"`
+ ShouldBeGrouped utilmarshal.StringFilter `json:"shouldBeGrouped"`
}
func (GroupByTraceSourceVisitor) Kind() string { return "GroupByTraceSourceVisitor" }
func (visitor GroupByTraceSourceVisitor) Enter(tree *tftree.SpanTree, span *model.Span) tftree.TreeVisitor {
- nestLevel, hasNestLevel := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel)
- if hasNestLevel && nestLevel.AsString() == pseudoSpanNestLevel {
+ pseudoType, hasPseudoType := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType)
+ if hasPseudoType && pseudoType.AsString() == myPseudoType {
// already grouped, don't recurse
return visitor
}
@@ -54,7 +55,7 @@ func (visitor GroupByTraceSourceVisitor) Enter(tree *tftree.SpanTree, span *mode
index := map[string][]model.Log{}
for _, log := range span.Logs {
traceSource, hasTraceSource := model.KeyValues(log.Fields).FindByKey(zconstants.TraceSource)
- if hasTraceSource && visitor.ShouldBeGrouped.Test(traceSource.AsString()) {
+ if hasTraceSource && visitor.ShouldBeGrouped.Matches(traceSource.AsString()) {
index[traceSource.AsString()] = append(index[traceSource.AsString()], log)
} else {
remainingLogs = append(remainingLogs, log)
@@ -74,9 +75,9 @@ func (visitor GroupByTraceSourceVisitor) Enter(tree *tftree.SpanTree, span *mode
Duration: span.Duration,
Tags: []model.KeyValue{
{
- Key: zconstants.NestLevel,
+ Key: zconstants.PseudoType,
VType: model.StringType,
- VStr: pseudoSpanNestLevel,
+ VStr: myPseudoType,
},
},
Logs: logs,
diff --git a/pkg/frontend/tf/defaults/step/object_tags.go b/pkg/frontend/tf/defaults/step/object_tags.go
index 46694609..134a6bb1 100644
--- a/pkg/frontend/tf/defaults/step/object_tags.go
+++ b/pkg/frontend/tf/defaults/step/object_tags.go
@@ -31,6 +31,7 @@ func init() {
)
}
+// Copy tags from child spans to the object.
type ObjectTagsVisitor struct {
ResourceTags []string `json:"resourceTags"`
}
@@ -38,7 +39,8 @@ type ObjectTagsVisitor struct {
func (ObjectTagsVisitor) Kind() string { return "ObjectTagsVisitor" }
func (visitor ObjectTagsVisitor) Enter(tree *tftree.SpanTree, span *model.Span) tftree.TreeVisitor {
- if tagKv, hasTag := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel); !hasTag || tagKv.VStr != zconstants.NestLevelObject {
+ if tagKv, isPseudo := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType); !isPseudo ||
+ tagKv.VStr != string(zconstants.PseudoTypeObject) {
return visitor
}
if _, hasTag := model.KeyValues(span.Tags).FindByKey("resource"); !hasTag {
@@ -61,10 +63,14 @@ func (visitor ObjectTagsVisitor) findTagRecursively(tree *tftree.SpanTree, span
for childId := range tree.Children(span.SpanID) {
childSpan := tree.Span(childId)
- if tagKv, hasTag := model.KeyValues(childSpan.Tags).FindByKey(zconstants.NestLevel); hasTag &&
- tagKv.VStr == zconstants.NestLevelObject {
- continue
+ {
+ tagKv, isPseudo := model.KeyValues(childSpan.Tags).FindByKey(zconstants.PseudoType)
+ if isPseudo && tagKv.VStr == string(zconstants.PseudoTypeObject) {
+ // do not copy from another object
+ continue
+ }
}
+
kv := visitor.findTagRecursively(tree, childSpan, tagKey)
if len(kv.Key) > 0 {
span.Tags = append(span.Tags, kv)
diff --git a/pkg/frontend/tf/defaults/step/prune_childless.go b/pkg/frontend/tf/defaults/step/prune_childless.go
index ed15d006..26742542 100644
--- a/pkg/frontend/tf/defaults/step/prune_childless.go
+++ b/pkg/frontend/tf/defaults/step/prune_childless.go
@@ -41,7 +41,7 @@ func (visitor PruneChildlessVisitor) Enter(tree *tftree.SpanTree, span *model.Sp
// Prune in postorder traversal to recursively remove higher pseudospans without leaves.
func (visitor PruneChildlessVisitor) Exit(tree *tftree.SpanTree, span *model.Span) {
- if _, hasTag := model.KeyValues(span.Tags).FindByKey(zconstants.NestLevel); hasTag {
+ if _, isPseudo := model.KeyValues(span.Tags).FindByKey(zconstants.PseudoType); isPseudo {
if len(tree.Children(span.SpanID)) == 0 && span.SpanID != tree.Root.SpanID {
tree.Delete(span.SpanID)
}
diff --git a/pkg/frontend/tf/defaults/step/util.go b/pkg/frontend/tf/defaults/step/util.go
deleted file mode 100644
index a3864870..00000000
--- a/pkg/frontend/tf/defaults/step/util.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2023 The Kelemetry Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tfstep
-
-import (
- "encoding/json"
- "strings"
-
- "k8s.io/apimachinery/pkg/util/sets"
-)
-
-type StringFilter struct {
- OneOf []string `json:"oneOf"`
- Negate bool `json:"negate"`
- IgnoreCase bool `json:"ignoreCase"`
-}
-
-// Test whether a string matches the filter.
-func (f *StringFilter) Test(s string) bool {
- for _, choice := range f.OneOf {
- if (f.IgnoreCase && strings.EqualFold(s, choice)) || s == choice {
- return !f.Negate
- }
- }
-
- return f.Negate
-}
-
-type JsonStringSet struct {
- set sets.Set[string]
-}
-
-func (set *JsonStringSet) UnmarshalJSON(buf []byte) error {
- strings := []string{}
- if err := json.Unmarshal(buf, &strings); err != nil {
- return err
- }
- set.set = sets.New[string](strings...)
- return nil
-}
diff --git a/pkg/frontend/tf/extension.go b/pkg/frontend/tf/extension.go
index f3a0e252..24875a0b 100644
--- a/pkg/frontend/tf/extension.go
+++ b/pkg/frontend/tf/extension.go
@@ -96,7 +96,7 @@ func (x *FetchExtensionsAndStoreCache) ProcessExtensions(
span := span
tags := model.KeyValues(span.Tags)
- if tag, exists := tags.FindByKey(zconstants.NestLevel); exists && tag.VStr == zconstants.NestLevelObject {
+ if tag, isPseudo := tags.FindByKey(zconstants.PseudoType); isPseudo && tag.VStr == string(zconstants.PseudoTypeObject) {
for extId, ext := range extensions {
ext := ext
diff --git a/pkg/frontend/tf/transform.go b/pkg/frontend/tf/transform.go
index 8472a0f8..4b1849ae 100644
--- a/pkg/frontend/tf/transform.go
+++ b/pkg/frontend/tf/transform.go
@@ -16,14 +16,12 @@ package transform
import (
"context"
- "errors"
"fmt"
"time"
"github.com/jaegertracing/jaeger/model"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
- "k8s.io/apimachinery/pkg/util/sets"
"github.com/kubewharf/kelemetry/pkg/frontend/extension"
tfconfig "github.com/kubewharf/kelemetry/pkg/frontend/tf/config"
@@ -38,8 +36,7 @@ func init() {
type TransformerOptions struct{}
-func (options *TransformerOptions) Setup(fs *pflag.FlagSet) {
-}
+func (options *TransformerOptions) Setup(fs *pflag.FlagSet) {}
func (options *TransformerOptions) EnableFlag() *bool { return nil }
@@ -74,34 +71,6 @@ func (transformer *Transformer) Transform(
tree := tftree.NewSpanTree(trace.Spans)
- transformer.groupDuplicates(tree)
-
- if config.UseSubtree && rootObject != nil {
- var rootSpan model.SpanID
- hasRootSpan := false
-
- for _, span := range tree.GetSpans() {
- if key, hasKey := utilobject.FromSpan(span); hasKey && key == *rootObject {
- rootSpan = span.SpanID
- hasRootSpan = true
- }
- }
-
- if hasRootSpan {
- if err := tree.SetRoot(rootSpan); err != nil {
- if errors.Is(err, tftree.ErrRootDoesNotExist) {
- return fmt.Errorf(
- "trace data does not contain desired root span %v as indicated by the exclusive flag (%w)",
- rootSpan,
- err,
- )
- }
-
- return fmt.Errorf("cannot set root: %w", err)
- }
- }
- }
-
newSpans, err := extensionProcessor.ProcessExtensions(ctx, transformer, config.Extensions, trace.Spans, start, end)
if err != nil {
return fmt.Errorf("cannot prepare extension trace: %w", err)
@@ -118,48 +87,3 @@ func (transformer *Transformer) Transform(
return nil
}
-
-// merge spans of the same object from multiple traces
-func (transformer *Transformer) groupDuplicates(tree *tftree.SpanTree) {
- commonSpans := map[utilobject.Key][]model.SpanID{}
-
- for _, span := range tree.GetSpans() {
- if key, hasKey := utilobject.FromSpan(span); hasKey {
- commonSpans[key] = append(commonSpans[key], span.SpanID)
- }
- }
-
- for _, spans := range commonSpans {
- if len(spans) > 1 {
- // only retain the first span
-
- desiredParent := spans[0]
-
- originalTags := tree.Span(desiredParent).Tags
- originalTagKeys := make(sets.Set[string], len(originalTags))
- for _, tag := range originalTags {
- originalTagKeys.Insert(tag.Key)
- }
-
- for _, obsoleteParent := range spans[1:] {
- for _, tag := range tree.Span(obsoleteParent).Tags {
- if !originalTagKeys.Has(tag.Key) {
- originalTags = append(originalTags, tag)
- originalTagKeys.Insert(tag.Key)
- }
- }
-
- children := tree.Children(obsoleteParent)
- for child := range children {
- tree.Move(child, desiredParent)
- }
-
- if tree.Root.SpanID == obsoleteParent {
- tree.Root = tree.Span(desiredParent)
- }
-
- tree.Delete(obsoleteParent)
- }
- }
- }
-}
diff --git a/pkg/frontend/tf/tree/tree.go b/pkg/frontend/tf/tree/tree.go
index c8327f42..dfd2ce61 100644
--- a/pkg/frontend/tf/tree/tree.go
+++ b/pkg/frontend/tf/tree/tree.go
@@ -15,6 +15,7 @@
package tftree
import (
+ "encoding/json"
"fmt"
"github.com/jaegertracing/jaeger/model"
@@ -68,6 +69,34 @@ func NewSpanTree(spans []*model.Span) *SpanTree {
return tree
}
+func (tree *SpanTree) Clone() (*SpanTree, error) {
+ copiedSpans := make([]*model.Span, 0, len(tree.spanMap))
+ for _, span := range tree.spanMap {
+ spanCopy, err := CopySpan(span)
+ if err != nil {
+ return nil, err
+ }
+
+ copiedSpans = append(copiedSpans, spanCopy)
+ }
+
+ return NewSpanTree(copiedSpans), nil
+}
+
+func CopySpan(span *model.Span) (*model.Span, error) {
+ spanJson, err := json.Marshal(span)
+ if err != nil {
+ return nil, err
+ }
+
+ var spanCopy *model.Span
+ if err := json.Unmarshal(spanJson, &spanCopy); err != nil {
+ return nil, err
+ }
+
+ return spanCopy, nil
+}
+
func (tree *SpanTree) Span(id model.SpanID) *model.Span { return tree.spanMap[id] }
func (tree *SpanTree) Children(id model.SpanID) map[model.SpanID]struct{} {
return tree.childrenMap[id]
@@ -133,7 +162,10 @@ func (subtree spanNode) visit(visitor TreeVisitor) {
panic("cannot visit nonexistent node in tree")
}
- subvisitor := visitor.Enter(subtree.tree, subtree.node)
+ var subvisitor TreeVisitor
+ if visitor != nil {
+ subvisitor = visitor.Enter(subtree.tree, subtree.node)
+ }
// enter before visitorStack is populated to allow removal
if _, stillExists := subtree.tree.spanMap[subtree.node.SpanID]; !stillExists {
@@ -169,7 +201,10 @@ func (subtree spanNode) visit(visitor TreeVisitor) {
delete(subtree.tree.visitorStack, subtree.node.SpanID)
- visitor.Exit(subtree.tree, subtree.node)
+ if visitor != nil {
+ visitor.Exit(subtree.tree, subtree.node)
+ }
+
if _, stillExists := subtree.tree.spanMap[subtree.node.SpanID]; !stillExists {
// deleted during exit
return
@@ -282,6 +317,24 @@ func (tree *SpanTree) Delete(spanId model.SpanID) {
}
}
+// Adds all spans in a tree as a subtree in this span.
+//
+// TODO FIXME: when the two trees have overlapping span IDs, this does not work correctly.
+func (tree *SpanTree) AddTree(childTree *SpanTree, parentId model.SpanID) {
+ if tree == childTree {
+ panic("cannot add tree to itself")
+ }
+
+ tree.addSubtree(parentId, childTree, childTree.Root.SpanID)
+}
+
+func (tree *SpanTree) addSubtree(parentId model.SpanID, otherTree *SpanTree, subroot model.SpanID) {
+ tree.Add(otherTree.Span(subroot), parentId)
+ for child := range otherTree.Children(subroot) {
+ tree.addSubtree(subroot, otherTree, child)
+ }
+}
+
type TreeVisitor interface {
// Called before entering the descendents of the span.
//
diff --git a/pkg/imports.go b/pkg/imports.go
index f309a279..f7a779ff 100644
--- a/pkg/imports.go
+++ b/pkg/imports.go
@@ -18,6 +18,8 @@ package kelemetry_pkg
import (
_ "github.com/kubewharf/kelemetry/pkg/aggregator/aggregatorevent"
_ "github.com/kubewharf/kelemetry/pkg/aggregator/eventdecorator/eventtagger"
+ _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job/local"
+ _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job/worker"
_ "github.com/kubewharf/kelemetry/pkg/aggregator/objectspandecorator/resourcetagger"
_ "github.com/kubewharf/kelemetry/pkg/aggregator/spancache/etcd"
_ "github.com/kubewharf/kelemetry/pkg/aggregator/spancache/local"
diff --git a/pkg/ownerlinker/linker.go b/pkg/ownerlinker/linker.go
index eaf0bc7b..9f055379 100644
--- a/pkg/ownerlinker/linker.go
+++ b/pkg/ownerlinker/linker.go
@@ -16,6 +16,7 @@ package ownerlinker
import (
"context"
+ "fmt"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
@@ -26,7 +27,9 @@ import (
"github.com/kubewharf/kelemetry/pkg/k8s/discovery"
"github.com/kubewharf/kelemetry/pkg/k8s/objectcache"
"github.com/kubewharf/kelemetry/pkg/manager"
+ "github.com/kubewharf/kelemetry/pkg/metrics"
utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+ "github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
func init() {
@@ -58,7 +61,8 @@ func (ctrl *Controller) Init() error { return nil }
func (ctrl *Controller) Start(ctx context.Context) error { return nil }
func (ctrl *Controller) Close(ctx context.Context) error { return nil }
-func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) *utilobject.Rich {
+func (ctrl *Controller) LinkerName() string { return "owner-linker" }
+func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) ([]linker.LinkerResult, error) {
raw := object.Raw
logger := ctrl.Logger.WithFields(object.AsFields("object"))
@@ -70,16 +74,17 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) *uti
raw, err = ctrl.ObjectCache.Get(ctx, object.VersionedKey)
if err != nil {
- logger.WithError(err).Error("cannot fetch object value")
- return nil
+ return nil, metrics.LabelError(fmt.Errorf("cannot fetch object value from cache: %w", err), "FetchCache")
}
if raw == nil {
logger.Debug("object no longer exists")
- return nil
+ return nil, nil
}
}
+ var results []linker.LinkerResult
+
for _, owner := range raw.GetOwnerReferences() {
if owner.Controller != nil && *owner.Controller {
groupVersion, err := schema.ParseGroupVersion(owner.APIVersion)
@@ -101,12 +106,12 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) *uti
continue
}
- ret := &utilobject.Rich{
+ parentRef := utilobject.Rich{
VersionedKey: utilobject.VersionedKey{
Key: utilobject.Key{
Cluster: object.Cluster,
Group: gvr.Group,
- Resource: gvr.Group,
+ Resource: gvr.Resource,
Namespace: object.Namespace,
Name: owner.Name,
},
@@ -114,11 +119,16 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) *uti
},
Uid: owner.UID,
}
- logger.WithField("owner", ret).Debug("Resolved owner")
-
- return ret
+ logger.WithField("owner", parentRef).Debug("Resolved owner")
+
+ results = append(results, linker.LinkerResult{
+ Object: parentRef,
+ Role: zconstants.LinkRoleParent,
+ Class: "children",
+ DedupId: "ownerReference",
+ })
}
}
- return nil
+ return results, nil
}
diff --git a/pkg/util/marshal/marshal.go b/pkg/util/marshal/marshal.go
new file mode 100644
index 00000000..3066a426
--- /dev/null
+++ b/pkg/util/marshal/marshal.go
@@ -0,0 +1,179 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Utilities for unmarshalling in config files
+package utilmarshal
+
+import (
+ "encoding/json"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "k8s.io/apimachinery/pkg/util/sets"
+
+ utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+)
+
+type Optional[T any] struct {
+ IsSet bool
+ Value T
+}
+
+func (opt Optional[T]) GetOr(defaultValue T) T {
+ if opt.IsSet {
+ return opt.Value
+ } else {
+ return defaultValue
+ }
+}
+
+func IsSetTo[T comparable](opt Optional[T], t T) bool {
+ return opt.IsSet && opt.Value == t
+}
+
+func (v *Optional[T]) UnmarshalJSON(buf []byte) error {
+ if err := json.Unmarshal(buf, &v.Value); err != nil {
+ return err
+ }
+
+ v.IsSet = true
+ return nil
+}
+
+type ObjectFilter struct {
+ Cluster Optional[StringFilter] `json:"cluster"`
+ Group Optional[StringFilter] `json:"group"`
+ Resource Optional[StringFilter] `json:"resource"`
+ Namespace Optional[StringFilter] `json:"namespace"`
+ Name Optional[StringFilter] `json:"name"`
+}
+
+func (filter *ObjectFilter) Matches(key utilobject.Key) bool {
+ if filter.Cluster.IsSet {
+ if !filter.Cluster.Value.Matches(key.Cluster) {
+ return false
+ }
+ }
+
+ if filter.Group.IsSet {
+ if !filter.Group.Value.Matches(key.Group) {
+ return false
+ }
+ }
+
+ if filter.Resource.IsSet {
+ if !filter.Resource.Value.Matches(key.Resource) {
+ return false
+ }
+ }
+
+ if filter.Namespace.IsSet {
+ if !filter.Namespace.Value.Matches(key.Namespace) {
+ return false
+ }
+ }
+
+ if filter.Name.IsSet {
+ if !filter.Name.Value.Matches(key.Name) {
+ return false
+ }
+ }
+
+ return true
+}
+
+type stringPredicate = func(string) bool
+
+type StringFilter struct {
+ fn stringPredicate
+}
+
+type fields struct {
+ Exact Optional[string] `json:"exact"`
+ OneOf Optional[[]string] `json:"oneOf"`
+ CaseFold Optional[string] `json:"caseInsensitive"`
+ Regex Optional[string] `json:"regex"`
+ Then Optional[bool] `json:"then"`
+}
+
+func (value fields) getBasePredicate() (stringPredicate, error) {
+ isSet := 0
+ for _, b := range []bool{value.Exact.IsSet, value.OneOf.IsSet, value.CaseFold.IsSet, value.Regex.IsSet} {
+ if b {
+ isSet += 1
+ }
+ }
+
+ if isSet > 1 {
+ return nil, fmt.Errorf("string filter must set exactly one of `exact`, `oneOf`, `caseInsensitive` or `regex`")
+ }
+
+ if value.Exact.IsSet {
+ return func(s string) bool { return s == value.Exact.Value }, nil
+ } else if value.OneOf.IsSet {
+ options := sets.New[string](value.OneOf.Value...)
+ return options.Has, nil
+ } else if value.CaseFold.IsSet {
+ return func(s string) bool { return strings.EqualFold(value.CaseFold.Value, value.CaseFold.Value) }, nil
+ } else if value.Regex.IsSet {
+ regex, err := regexp.Compile(value.Regex.Value)
+ if err != nil {
+ return nil, fmt.Errorf("pattern contains invalid regex: %w", err)
+ }
+
+ return regex.MatchString, nil
+ } else {
+ return func(string) bool { return true }, nil // caller will change value to `then`
+ }
+}
+
+func (f *StringFilter) UnmarshalJSON(buf []byte) error {
+ var pattern string
+ if err := json.Unmarshal(buf, &pattern); err == nil {
+ f.fn = func(s string) bool { return s == pattern }
+ return nil
+ }
+
+ var value fields
+ if err := json.Unmarshal(buf, &value); err != nil {
+ return err
+ }
+
+ predicate, err := value.getBasePredicate()
+ if err != nil {
+ return err
+ }
+
+ then := value.Then.GetOr(true)
+ f.fn = func(s string) bool {
+ base := predicate(s)
+ if base {
+ return then
+ } else {
+ return !then
+ }
+ }
+
+ return nil
+}
+
+func (f *StringFilter) Matches(subject string) bool {
+ if f.fn == nil {
+ // string filter not set, take default then = true
+ return true
+ }
+
+ return f.fn(subject)
+}
diff --git a/pkg/util/object/key.go b/pkg/util/object/key.go
index 1208c076..bb208a6c 100644
--- a/pkg/util/object/key.go
+++ b/pkg/util/object/key.go
@@ -18,13 +18,9 @@ import (
"fmt"
"strings"
- "github.com/jaegertracing/jaeger/model"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/sets"
-
- "github.com/kubewharf/kelemetry/pkg/util/zconstants"
)
type Key struct {
@@ -80,39 +76,6 @@ func FromMap(tags map[string]string) (key Key, ok bool) {
return key, true
}
-func FromSpan(span *model.Span) (Key, bool) {
- tags := model.KeyValues(span.Tags)
- traceSource, hasTraceSource := tags.FindByKey(zconstants.TraceSource)
- if !hasTraceSource || traceSource.VStr != zconstants.TraceSourceObject {
- return Key{}, false
- }
-
- cluster, _ := tags.FindByKey("cluster")
- group, _ := tags.FindByKey("group")
- resource, _ := tags.FindByKey("resource")
- namespace, _ := tags.FindByKey("namespace")
- name, _ := tags.FindByKey("name")
- key := Key{
- Cluster: cluster.VStr,
- Group: group.VStr,
- Resource: resource.VStr,
- Namespace: namespace.VStr,
- Name: name.VStr,
- }
- return key, true
-}
-
-func FromSpans(spans []*model.Span) sets.Set[Key] {
- keys := sets.New[Key]()
-
- for _, span := range spans {
- if key, ok := FromSpan(span); ok {
- keys.Insert(key)
- }
- }
- return keys
-}
-
type VersionedKey struct {
Key
Version string `json:"version"`
diff --git a/pkg/util/reflect/reflect.go b/pkg/util/reflect/reflect.go
index 5e3f60a1..3ce6179d 100644
--- a/pkg/util/reflect/reflect.go
+++ b/pkg/util/reflect/reflect.go
@@ -23,3 +23,5 @@ func TypeOf[T any]() reflect.Type {
}
func ZeroOf[T any]() (_ T) { return }
+
+func Identity[T any](t T) T { return t }
diff --git a/pkg/util/semaphore/semaphore.go b/pkg/util/semaphore/semaphore.go
index c04070c5..da68928b 100644
--- a/pkg/util/semaphore/semaphore.go
+++ b/pkg/util/semaphore/semaphore.go
@@ -108,10 +108,23 @@ func (sem *Semaphore) Schedule(task Task) {
}
} else {
if publish != nil {
+ sem.doneWg.Add(1)
+ wrappedPublish := func() error {
+ defer sem.doneWg.Done()
+ return publish()
+ }
+
select {
- case sem.publishCh <- publish:
+ case sem.publishCh <- wrappedPublish:
+ // publishCh has zero capacity, so this case blocks until the main goroutine selects the publishCh case,
+ // so we can ensure that publishCh is received before calling sem.doneWg.Done().
+ // However we need to call doneWg again to ensure that
+ // schedules during publish are called before this function brings doneWg to zero.
+
+ // the main goroutine will call `sem.doneWg.Done()` for us.
case <-sem.errNotifyCh:
// no need to publish if the caller received error
+ sem.doneWg.Done()
}
}
}
diff --git a/pkg/util/zconstants/link.go b/pkg/util/zconstants/link.go
new file mode 100644
index 00000000..dd17ffa5
--- /dev/null
+++ b/pkg/util/zconstants/link.go
@@ -0,0 +1,130 @@
+// Copyright 2023 The Kelemetry Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package zconstants
+
+import (
+ "github.com/jaegertracing/jaeger/model"
+
+ utilobject "github.com/kubewharf/kelemetry/pkg/util/object"
+)
+
+type LinkRef struct {
+ Key utilobject.Key
+ Role LinkRoleValue
+ Class string
+}
+
+// Tags for TraceSourceLink spans that indicate the linked object.
+const (
+ LinkedObjectCluster = "linkedCluster"
+ LinkedObjectGroup = "linkedGroup"
+ LinkedObjectResource = "linkedResource"
+ LinkedObjectNamespace = "linkedNamespace"
+ LinkedObjectName = "linkedName"
+
+ // Indicates how the linked trace interacts with the current trace.
+ LinkRole = "linkRole"
+
+ // If this tag is nonempty, a virtual span is inserted between the linked objects with the tag value as the name.
+ LinkClass = "linkClass"
+)
+
+func TagLinkedObject(tags map[string]string, ln LinkRef) {
+ tags[LinkedObjectCluster] = ln.Key.Cluster
+ tags[LinkedObjectGroup] = ln.Key.Group
+ tags[LinkedObjectResource] = ln.Key.Resource
+ tags[LinkedObjectNamespace] = ln.Key.Namespace
+ tags[LinkedObjectName] = ln.Key.Name
+ tags[LinkRole] = string(ln.Role)
+ tags[LinkClass] = ln.Class
+}
+
+func ObjectKeyFromSpan(span *model.Span) utilobject.Key {
+ tags := model.KeyValues(span.Tags)
+
+ cluster, _ := tags.FindByKey("cluster")
+ group, _ := tags.FindByKey("group")
+ resource, _ := tags.FindByKey("resource")
+ namespace, _ := tags.FindByKey("namespace")
+ name, _ := tags.FindByKey("name")
+ key := utilobject.Key{
+ Cluster: cluster.VStr,
+ Group: group.VStr,
+ Resource: resource.VStr,
+ Namespace: namespace.VStr,
+ Name: name.VStr,
+ }
+ return key
+}
+
+func LinkedKeyFromSpan(span *model.Span) (utilobject.Key, bool) {
+ tags := model.KeyValues(span.Tags)
+ pseudoType, isPseudo := tags.FindByKey(PseudoType)
+ if !isPseudo || pseudoType.VStr != string(PseudoTypeLink) {
+ return utilobject.Key{}, false
+ }
+
+ cluster, _ := tags.FindByKey(LinkedObjectCluster)
+ group, _ := tags.FindByKey(LinkedObjectGroup)
+ resource, _ := tags.FindByKey(LinkedObjectResource)
+ namespace, _ := tags.FindByKey(LinkedObjectNamespace)
+ name, _ := tags.FindByKey(LinkedObjectName)
+ key := utilobject.Key{
+ Cluster: cluster.VStr,
+ Group: group.VStr,
+ Resource: resource.VStr,
+ Namespace: namespace.VStr,
+ Name: name.VStr,
+ }
+ return key, true
+}
+
+func KeyToSpanTags(key utilobject.Key) map[string]string {
+ return map[string]string{
+ "cluster": key.Cluster,
+ "group": key.Group,
+ "resource": key.Resource,
+ "namespace": key.Namespace,
+ "name": key.Name,
+ }
+}
+
+func VersionedKeyToSpanTags(key utilobject.VersionedKey) map[string]string {
+ m := KeyToSpanTags(key.Key)
+ m["version"] = key.Version
+ return m
+}
+
+type LinkRoleValue string
+
+const (
+ // The current trace is a child trace under the linked trace
+ LinkRoleParent LinkRoleValue = "parent"
+
+ // The linked trace is a child trace under the current trace.
+ LinkRoleChild LinkRoleValue = "child"
+)
+
+// Determines the role of the reverse link.
+func ReverseLinkRole(role LinkRoleValue) LinkRoleValue {
+ switch role {
+ case LinkRoleParent:
+ return LinkRoleChild
+ case LinkRoleChild:
+ return LinkRoleParent
+ default:
+ return role
+ }
+}
diff --git a/pkg/util/zconstants/zconstants.go b/pkg/util/zconstants/zconstants.go
index bfac7185..02faeb5a 100644
--- a/pkg/util/zconstants/zconstants.go
+++ b/pkg/util/zconstants/zconstants.go
@@ -16,7 +16,9 @@
// for span transformation in the frontend storage plugin.
package zconstants
-import "time"
+import (
+ "time"
+)
// All tags with this prefix are not rendered.
const Prefix = "zzz-"
@@ -26,10 +28,23 @@ const SpanName = Prefix + "kelemetryName"
// Indicates that the current span is a pseudospan that can be folded or flattened.
// The value is the folding type.
-const NestLevel = Prefix + "nestingLevel"
+const PseudoType = Prefix + "pseudoType"
+
+// Indicates that the current span is not pseudo.
+// Used to optimize trace listing.
+//
+// This constant is used as both tag key and value.
+const NotPseudo = Prefix + "notPseudo"
+
+type PseudoTypeValue string
const (
- NestLevelObject = "object"
+ // Root span in an object trace.
+ PseudoTypeObject PseudoTypeValue = "object"
+ // Indicate that another trace shall be included.
+ PseudoTypeLink PseudoTypeValue = "link"
+ // A virtual span synthesized in the frontend when link class is nonempty.
+ PseudoTypeLinkClass PseudoTypeValue = "linkClass"
)
// Identifies that the span represents an actual event (rather than as a pseudospan).
@@ -37,11 +52,14 @@ const TraceSource = Prefix + "traceSource"
const (
TraceSourceObject = "object"
- TraceSourceAudit = "audit"
- TraceSourceEvent = "event"
+
+ TraceSourceAudit = "audit"
+ TraceSourceEvent = "event"
)
func KnownTraceSources(withPseudo bool) []string {
+ numPseudoTraceSources := 1
+
traceSources := []string{
// pseudo
TraceSourceObject,
@@ -52,7 +70,7 @@ func KnownTraceSources(withPseudo bool) []string {
}
if !withPseudo {
- traceSources = traceSources[1:]
+ traceSources = traceSources[numPseudoTraceSources:]
}
return traceSources
diff --git a/quickstart.docker-compose.yaml b/quickstart.docker-compose.yaml
index dbaf8a37..8272a2fd 100644
--- a/quickstart.docker-compose.yaml
+++ b/quickstart.docker-compose.yaml
@@ -70,6 +70,7 @@ services:
"--diff-cache-wrapper-enable",
"--diff-controller-leader-election-enable=false",
"--event-informer-leader-election-enable=false",
+ "--linker-worker-count=8",
"--span-cache=etcd",
"--span-cache-etcd-endpoints=etcd:2379",
"--tracer-otel-endpoint=jaeger-collector:4317",