Repository: open-telemetry/opentelemetry-ebpf-instrumentation
Pull request #46: Support k8s.job.name and k8s.cronjob.name attributes
Author: login: cuichenli
Created: 2025-05-14T05:14:07Z | Merged: 2025-05-20T12:27:19Z

related to https://github.com/grafana/beyla/issues/1603


Diff:
diff --git a/pkg/export/attributes/attr_defs.go b/pkg/export/attributes/attr_defs.go
index 3309269666..2e7bb67cbd 100644
--- a/pkg/export/attributes/attr_defs.go
+++ b/pkg/export/attributes/attr_defs.go
@@ -130,6 +130,8 @@ func getDefinitions(groups AttrGroups) map[Section]AttrReportGroup {
 			attr.K8sContainerName:   true,
 			attr.K8sDeploymentName:  true,
 			attr.K8sReplicaSetName:  true,
+			attr.K8sJobName:         true,
+			attr.K8sCronJobName:     true,
 			attr.K8sDaemonSetName:   true,
 			attr.K8sStatefulSetName: true,
 			attr.K8sNodeName:        true,
diff --git a/pkg/export/attributes/names/attrs.go b/pkg/export/attributes/names/attrs.go
index 791207c553..20ff57a49f 100644
--- a/pkg/export/attributes/names/attrs.go
+++ b/pkg/export/attributes/names/attrs.go
@@ -59,6 +59,8 @@ const (
 	K8sContainerName   = Name("k8s.container.name")
 	K8sDeploymentName  = Name("k8s.deployment.name")
 	K8sReplicaSetName  = Name("k8s.replicaset.name")
+	K8sJobName         = Name("k8s.job.name")
+	K8sCronJobName     = Name("k8s.cronjob.name")
 	K8sDaemonSetName   = Name("k8s.daemonset.name")
 	K8sStatefulSetName = Name("k8s.statefulset.name")
 	K8sOwnerName       = Name("k8s.owner.name")
diff --git a/pkg/export/prom/prom.go b/pkg/export/prom/prom.go
index c8e64cf638..fde333c2ca 100644
--- a/pkg/export/prom/prom.go
+++ b/pkg/export/prom/prom.go
@@ -58,6 +58,8 @@ const (
 	k8sStatefulSetName = "k8s_statefulset_name"
 	k8sReplicaSetName  = "k8s_replicaset_name"
 	k8sDaemonSetName   = "k8s_daemonset_name"
+	k8sJobName         = "k8s_job_name"
+	k8sCronJobName     = "k8s_cronjob_name"
 	k8sNodeName        = "k8s_node_name"
 	k8sPodUID          = "k8s_pod_uid"
 	k8sPodStartTime    = "k8s_pod_start_time"
@@ -861,7 +863,7 @@ func (r *metricsReporter) observe(span *request.Span) {
 
 func appendK8sLabelNames(names []string) []string {
 	names = append(names, k8sNamespaceName, k8sPodName, k8sContainerName, k8sNodeName, k8sPodUID, k8sPodStartTime,
-		k8sDeploymentName, k8sReplicaSetName, k8sStatefulSetName, k8sDaemonSetName, k8sClusterName)
+		k8sDeploymentName, k8sReplicaSetName, k8sStatefulSetName, k8sJobName, k8sCronJobName, k8sDaemonSetName, k8sClusterName)
 	return names
 }
 
@@ -874,6 +876,8 @@ func appendK8sLabelValuesService(values []string, service svc.Attrs) []string {
 		service.Metadata[(attr.K8sNodeName)],
 		service.Metadata[(attr.K8sPodUID)],
 		service.Metadata[(attr.K8sPodStartTime)],
+		service.Metadata[(attr.K8sJobName)],
+		service.Metadata[(attr.K8sCronJobName)],
 		service.Metadata[(attr.K8sDeploymentName)],
 		service.Metadata[(attr.K8sReplicaSetName)],
 		service.Metadata[(attr.K8sStatefulSetName)],
diff --git a/pkg/kubecache/meta/types.go b/pkg/kubecache/meta/types.go
index 2c2c7f2768..9a0f85e1a5 100644
--- a/pkg/kubecache/meta/types.go
+++ b/pkg/kubecache/meta/types.go
@@ -36,6 +36,15 @@ func ownersFrom(meta *metav1.ObjectMeta) []*informer.Owner {
 				return owners
 			}
 		}
+		if or.APIVersion == "batch/v1" && or.Kind == "Job" {
+			// we heuristically extract the CronJob name from the Job name
+			if idx := strings.LastIndexByte(or.Name, '-'); idx > 0 {
+				owners = append(owners, &informer.Owner{Kind: "CronJob", Name: or.Name[:idx]})
+				// we already have what we need for decoration and selection. Ignoring any other owner
+				// it might hypothetically have (it would be a rare case)
+				return owners
+			}
+		}
 	}
 	return owners
 }
diff --git a/pkg/services/criteria.go b/pkg/services/criteria.go
index e0d1b0ddc0..4210900771 100644
--- a/pkg/services/criteria.go
+++ b/pkg/services/criteria.go
@@ -18,6 +18,8 @@ const (
 	AttrReplicaSetName  = "k8s_replicaset_name"
 	AttrDaemonSetName   = "k8s_daemonset_name"
 	AttrStatefulSetName = "k8s_statefulset_name"
+	AttrJobName         = "k8s_job_name"
+	AttrCronJobName     = "k8s_cronjob_name"
 	// AttrOwnerName would be a generic search criteria that would
 	// match against deployment, replicaset, daemonset and statefulset names
 	AttrOwnerName = "k8s_owner_name"
@@ -31,6 +33,8 @@ var allowedAttributeNames = map[string]struct{}{
 	AttrReplicaSetName:  {},
 	AttrDaemonSetName:   {},
 	AttrStatefulSetName: {},
+	AttrJobName:         {},
+	AttrCronJobName:     {},
 	AttrOwnerName:       {},
 }
 
diff --git a/pkg/transform/k8s.go b/pkg/transform/k8s.go
index a8af57c378..9032641aa2 100644
--- a/pkg/transform/k8s.go
+++ b/pkg/transform/k8s.go
@@ -198,6 +198,10 @@ func OwnerLabelName(kind string) attr.Name {
 		return attr.K8sDaemonSetName
 	case "ReplicaSet":
 		return attr.K8sReplicaSetName
+	case "Job":
+		return attr.K8sJobName
+	case "CronJob":
+		return attr.K8sCronJobName
 	default:
 		return ""
 	}
diff --git a/test/integration/k8s/manifests/05-uninstrumented-cronjob.yml b/test/integration/k8s/manifests/05-uninstrumented-cronjob.yml
new file mode 100644
index 0000000000..39b0b7ac2d
--- /dev/null
+++ b/test/integration/k8s/manifests/05-uninstrumented-cronjob.yml
@@ -0,0 +1,44 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: cronjobservice
+spec:
+  selector:
+    app: cronjobservice
+  ports:
+    - port: 8083
+      name: http
+      targetPort: http
+---
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+  name: cronjobservice
+  labels:
+    app: cronjobservice
+spec:
+  concurrencyPolicy: Forbid 
+  schedule: "* * * * *"
+  jobTemplate:
+    spec:
+      template:
+        metadata:
+          name: cronjobservice
+          labels:
+            app: cronjobservice
+          annotations:
+            resource.opentelemetry.io/deployment.environment: 'integration-test'
+            resource.opentelemetry.io/service.version: '3.2.1'
+        spec:
+          restartPolicy: Never
+          containers:
+            - name: cronjobservice
+              image: testserver:dev
+              imagePullPolicy: Never # loaded into Kind from localhost
+              ports:
+                - containerPort: 8083
+                  hostPort: 8083
+                  name: http
+              env:
+                - name: LOG_LEVEL
+                  value: "DEBUG"
diff --git a/test/integration/k8s/manifests/05-uninstrumented-job.yml b/test/integration/k8s/manifests/05-uninstrumented-job.yml
new file mode 100644
index 0000000000..efa8ebae80
--- /dev/null
+++ b/test/integration/k8s/manifests/05-uninstrumented-job.yml
@@ -0,0 +1,40 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: jobservice
+spec:
+  selector:
+    app: jobservice
+  ports:
+    - port: 8082
+      name: http
+      targetPort: http
+---
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: jobservice
+  labels:
+    app: jobservice
+spec:
+  template:
+    metadata:
+      name: jobservice
+      labels:
+        app: jobservice
+      annotations:
+        resource.opentelemetry.io/deployment.environment: 'integration-test'
+        resource.opentelemetry.io/service.version: '3.2.1'
+    spec:
+      restartPolicy: Never
+      containers:
+        - name: jobservice
+          image: testserver:dev
+          imagePullPolicy: Never # loaded into Kind from localhost
+          ports:
+            - containerPort: 8082
+              hostPort: 8082
+              name: http
+          env:
+            - name: LOG_LEVEL
+              value: "DEBUG"
diff --git a/test/integration/k8s/manifests/06-beyla-daemonset.yml b/test/integration/k8s/manifests/06-beyla-daemonset.yml
index 229782cf4e..dfcc117b6d 100644
--- a/test/integration/k8s/manifests/06-beyla-daemonset.yml
+++ b/test/integration/k8s/manifests/06-beyla-daemonset.yml
@@ -17,6 +17,8 @@ data:
         # used in the k8s_owners_*_test.go
         - k8s_statefulset_name: statefulservice
         - k8s_daemonset_name: dsservice
+        - k8s_job_name: jobservice
+        - k8s_cronjob_name: cronjobservice
       exclude_services:
         # testing service exclusion, by expecting that K8s discovery only picks the "otherinstance"
         # deployment (ignoring the "testserver" in the other pod)
diff --git a/test/integration/k8s/owners/k8s_cronjob_metadata_test.go b/test/integration/k8s/owners/k8s_cronjob_metadata_test.go
new file mode 100644
index 0000000000..20bb617846
--- /dev/null
+++ b/test/integration/k8s/owners/k8s_cronjob_metadata_test.go
@@ -0,0 +1,93 @@
+//go:build integration_k8s
+
+package owners
+
+import (
+	"context"
+	"encoding/json"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/mariomac/guara/pkg/test"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"sigs.k8s.io/e2e-framework/pkg/envconf"
+	"sigs.k8s.io/e2e-framework/pkg/features"
+
+	"github.com/open-telemetry/opentelemetry-ebpf-instrumentation/test/integration/components/jaeger"
+	k8s "github.com/open-telemetry/opentelemetry-ebpf-instrumentation/test/integration/k8s/common"
+)
+
+// For the DaemonSet scenario, we only check that Beyla is able to instrument any
+// process in the system. We just check that traces are properly generated without
+// entering in too many details
+func TestCronJobMetadata(t *testing.T) {
+	feat := features.New("Beyla is able to decorate the metadata of a cronjob").
+		Assess("it sends decorated traces for the cronjob",
+			func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context {
+				test.Eventually(t, testTimeout, func(t require.TestingT) {
+					// Invoking both service instances, but we will expect that only one
+					// is instrumented, according to the discovery mechanisms
+					resp, err := http.Get("http://localhost:38083/pingpong")
+					require.NoError(t, err)
+					require.Equal(t, http.StatusOK, resp.StatusCode)
+
+					resp, err = http.Get(jaegerQueryURL + "?service=cronjobservice")
+					require.NoError(t, err)
+					if resp == nil {
+						return
+					}
+					require.Equal(t, http.StatusOK, resp.StatusCode)
+					var tq jaeger.TracesQuery
+					require.NoError(t, json.NewDecoder(resp.Body).Decode(&tq))
+					traces := tq.FindBySpan(jaeger.Tag{Key: "url.path", Type: "string", Value: "/pingpong"})
+					require.NotEmpty(t, traces)
+					trace := traces[0]
+					require.NotEmpty(t, trace.Spans)
+
+					// Check that the service.namespace is set from the K8s namespace
+					assert.Len(t, trace.Processes, 1)
+					for _, proc := range trace.Processes {
+						sd := jaeger.DiffAsRegexp([]jaeger.Tag{
+							{Key: "service.namespace", Type: "string", Value: "^default$"},
+							{Key: "service.instance.id", Type: "string", Value: "^default\\.cronjobservice-.+\\.cronjobservice"},
+						}, proc.Tags)
+						require.Empty(t, sd)
+					}
+
+					// Check the information of the parent span
+					res := trace.FindByOperationName("GET /pingpong", "server")
+					require.Len(t, res, 1)
+					parent := res[0]
+					sd := jaeger.DiffAsRegexp([]jaeger.Tag{
+						{Key: "k8s.pod.name", Type: "string", Value: "^cronjobservice-.*"},
+						{Key: "k8s.container.name", Type: "string", Value: "cronjobservice"},
+						{Key: "k8s.node.name", Type: "string", Value: ".+-control-plane$"},
+						{Key: "k8s.pod.uid", Type: "string", Value: k8s.UUIDRegex},
+						{Key: "k8s.pod.start_time", Type: "string", Value: k8s.TimeRegex},
+						{Key: "k8s.cronjob.name", Type: "string", Value: "^cronjobservice$"},
+						{Key: "k8s.namespace.name", Type: "string", Value: "^default$"},
+						{Key: "k8s.cluster.name", Type: "string", Value: "^beyla-k8s-test-cluster$"},
+						{Key: "service.namespace", Type: "string", Value: "^default$"},
+						{Key: "service.instance.id", Type: "string", Value: "^default\\.cronjobservice-.+\\.cronjobservice"},
+					}, trace.Processes[parent.ProcessID].Tags)
+					require.Empty(t, sd)
+
+					// check that no other labels are added
+					sd = jaeger.DiffAsRegexp([]jaeger.Tag{
+						{Key: "k8s.deployment.name", Type: "string"},
+						{Key: "k8s.daemonset.name", Type: "string"},
+						{Key: "k8s.statefulset.name", Type: "string"},
+					}, trace.Processes[parent.ProcessID].Tags)
+					require.Equal(t, jaeger.DiffResult{
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.deployment.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.daemonset.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.statefulset.name", Type: "string"}},
+					}, sd)
+				}, test.Interval(100*time.Millisecond))
+				return ctx
+			},
+		).Feature()
+	cluster.TestEnv().Test(t, feat)
+}
diff --git a/test/integration/k8s/owners/k8s_daemonset_metadata_test.go b/test/integration/k8s/owners/k8s_daemonset_metadata_test.go
index b8444123c9..bd34d67166 100644
--- a/test/integration/k8s/owners/k8s_daemonset_metadata_test.go
+++ b/test/integration/k8s/owners/k8s_daemonset_metadata_test.go
@@ -78,10 +78,14 @@ func TestDaemonSetMetadata(t *testing.T) {
 					sd = jaeger.DiffAsRegexp([]jaeger.Tag{
 						{Key: "k8s.deployment.name", Type: "string"},
 						{Key: "k8s.statefulset.name", Type: "string"},
+						{Key: "k8s.job.name", Type: "string"},
+						{Key: "k8s.cronjob.name", Type: "string"},
 					}, trace.Processes[parent.ProcessID].Tags)
 					require.Equal(t, jaeger.DiffResult{
 						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.deployment.name", Type: "string"}},
 						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.statefulset.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.job.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.cronjob.name", Type: "string"}},
 					}, sd)
 				}, test.Interval(100*time.Millisecond))
 				return ctx
diff --git a/test/integration/k8s/owners/k8s_job_metadata_test.go b/test/integration/k8s/owners/k8s_job_metadata_test.go
new file mode 100644
index 0000000000..acf670d690
--- /dev/null
+++ b/test/integration/k8s/owners/k8s_job_metadata_test.go
@@ -0,0 +1,95 @@
+//go:build integration_k8s
+
+package owners
+
+import (
+	"context"
+	"encoding/json"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/mariomac/guara/pkg/test"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"sigs.k8s.io/e2e-framework/pkg/envconf"
+	"sigs.k8s.io/e2e-framework/pkg/features"
+
+	"github.com/open-telemetry/opentelemetry-ebpf-instrumentation/test/integration/components/jaeger"
+	k8s "github.com/open-telemetry/opentelemetry-ebpf-instrumentation/test/integration/k8s/common"
+)
+
+// For the DaemonSet scenario, we only check that Beyla is able to instrument any
+// process in the system. We just check that traces are properly generated without
+// entering in too many details
+func TestJobMetadata(t *testing.T) {
+	feat := features.New("Beyla is able to decorate the metadata of a job").
+		Assess("it sends decorated traces for the job",
+			func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context {
+				test.Eventually(t, testTimeout, func(t require.TestingT) {
+					// Invoking both service instances, but we will expect that only one
+					// is instrumented, according to the discovery mechanisms
+					resp, err := http.Get("http://localhost:38082/pingpong")
+					require.NoError(t, err)
+					require.Equal(t, http.StatusOK, resp.StatusCode)
+
+					resp, err = http.Get(jaegerQueryURL + "?service=jobservice")
+					require.NoError(t, err)
+					if resp == nil {
+						return
+					}
+					require.Equal(t, http.StatusOK, resp.StatusCode)
+					var tq jaeger.TracesQuery
+					require.NoError(t, json.NewDecoder(resp.Body).Decode(&tq))
+					traces := tq.FindBySpan(jaeger.Tag{Key: "url.path", Type: "string", Value: "/pingpong"})
+					require.NotEmpty(t, traces)
+					trace := traces[0]
+					require.NotEmpty(t, trace.Spans)
+
+					// Check that the service.namespace is set from the K8s namespace
+					assert.Len(t, trace.Processes, 1)
+					for _, proc := range trace.Processes {
+						sd := jaeger.DiffAsRegexp([]jaeger.Tag{
+							{Key: "service.namespace", Type: "string", Value: "^default$"},
+							{Key: "service.instance.id", Type: "string", Value: "^default\\.jobservice-.+\\.jobservice"},
+						}, proc.Tags)
+						require.Empty(t, sd)
+					}
+
+					// Check the information of the parent span
+					res := trace.FindByOperationName("GET /pingpong", "server")
+					require.Len(t, res, 1)
+					parent := res[0]
+					sd := jaeger.DiffAsRegexp([]jaeger.Tag{
+						{Key: "k8s.pod.name", Type: "string", Value: "^jobservice-.*"},
+						{Key: "k8s.container.name", Type: "string", Value: "jobservice"},
+						{Key: "k8s.node.name", Type: "string", Value: ".+-control-plane$"},
+						{Key: "k8s.pod.uid", Type: "string", Value: k8s.UUIDRegex},
+						{Key: "k8s.pod.start_time", Type: "string", Value: k8s.TimeRegex},
+						{Key: "k8s.job.name", Type: "string", Value: "^jobservice$"},
+						{Key: "k8s.namespace.name", Type: "string", Value: "^default$"},
+						{Key: "k8s.cluster.name", Type: "string", Value: "^beyla-k8s-test-cluster$"},
+						{Key: "service.namespace", Type: "string", Value: "^default$"},
+						{Key: "service.instance.id", Type: "string", Value: "^default\\.jobservice-.+\\.jobservice"},
+					}, trace.Processes[parent.ProcessID].Tags)
+					require.Empty(t, sd)
+
+					// check that no other labels are added
+					sd = jaeger.DiffAsRegexp([]jaeger.Tag{
+						{Key: "k8s.deployment.name", Type: "string"},
+						{Key: "k8s.daemonset.name", Type: "string"},
+						{Key: "k8s.statefulset.name", Type: "string"},
+						{Key: "k8s.cronjob.name", Type: "string"},
+					}, trace.Processes[parent.ProcessID].Tags)
+					require.Equal(t, jaeger.DiffResult{
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.deployment.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.daemonset.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.statefulset.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.cronjob.name", Type: "string"}},
+					}, sd)
+				}, test.Interval(100*time.Millisecond))
+				return ctx
+			},
+		).Feature()
+	cluster.TestEnv().Test(t, feat)
+}
diff --git a/test/integration/k8s/owners/k8s_owners_main_test.go b/test/integration/k8s/owners/k8s_owners_main_test.go
index 3c21fe7a94..238308d169 100644
--- a/test/integration/k8s/owners/k8s_owners_main_test.go
+++ b/test/integration/k8s/owners/k8s_owners_main_test.go
@@ -32,6 +32,7 @@ func TestMain(m *testing.M) {
 		docker.ImageBuild{Tag: "quay.io/prometheus/prometheus:v2.55.1"},
 		docker.ImageBuild{Tag: "otel/opentelemetry-collector-contrib:0.103.0"},
 		docker.ImageBuild{Tag: "jaegertracing/all-in-one:1.57"},
+		docker.ImageBuild{Tag: "grpcpinger:dev", Dockerfile: k8s.DockerfilePinger},
 	); err != nil {
 		slog.Error("can't build docker images", "error", err)
 		os.Exit(-1)
@@ -51,6 +52,8 @@ func TestMain(m *testing.M) {
 		kube.Deploy(testpath.Manifests+"/04-jaeger.yml"),
 		kube.Deploy(testpath.Manifests+"/05-uninstrumented-statefulset.yml"),
 		kube.Deploy(testpath.Manifests+"/05-uninstrumented-daemonset.yml"),
+		kube.Deploy(testpath.Manifests+"/05-uninstrumented-job.yml"),
+		kube.Deploy(testpath.Manifests+"/05-uninstrumented-cronjob.yml"),
 		kube.Deploy(testpath.Manifests+"/06-beyla-daemonset.yml"),
 	)
 
diff --git a/test/integration/k8s/owners/k8s_statefulset_metadata_test.go b/test/integration/k8s/owners/k8s_statefulset_metadata_test.go
index a6d66a2aef..0a6bd4a621 100644
--- a/test/integration/k8s/owners/k8s_statefulset_metadata_test.go
+++ b/test/integration/k8s/owners/k8s_statefulset_metadata_test.go
@@ -78,10 +78,14 @@ func TestStatefulSetMetadata(t *testing.T) {
 					sd = jaeger.DiffAsRegexp([]jaeger.Tag{
 						{Key: "k8s.deployment.name", Type: "string"},
 						{Key: "k8s.daemonset.name", Type: "string"},
+						{Key: "k8s.job.name", Type: "string"},
+						{Key: "k8s.cronjob.name", Type: "string"},
 					}, trace.Processes[parent.ProcessID].Tags)
 					require.Equal(t, jaeger.DiffResult{
 						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.deployment.name", Type: "string"}},
 						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.daemonset.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.job.name", Type: "string"}},
+						{ErrType: jaeger.ErrTypeMissing, Expected: jaeger.Tag{Key: "k8s.cronjob.name", Type: "string"}},
 					}, sd)
 				}, test.Interval(100*time.Millisecond))
 				return ctx


Reviews:
user: login: mariomac
body: Amazing!! I just have a minor suggestion about a code block.

Thanks for your contribution!!
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T09:44:43Z
user: login: cuichenli
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T09:51:10Z
user: login: mariomac
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:08:08Z
user: login: mariomac
body: 🚢 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:08:30Z

Review comments:
diff_hunk: @@ -36,6 +36,15 @@ func ownersFrom(meta *metav1.ObjectMeta) []*informer.Owner {
 				return owners
 			}
 		}
+		if or.APIVersion == "batch/v1" && or.Kind == "Job" {
+			// we heuristically extract the CronJob name from the Job name
+			if idx := strings.LastIndexByte(or.Name, '-'); idx > 0 {
+				owners = append(owners, &informer.Owner{Kind: "CronJob", Name: or.Name[:idx]})
+				// we already have what we need for decoration and selection. Ignoring any other owner
+				// it might hypothetically have (it would be a rare case)
+				return owners
+			}
+		}
path: pkg/kubecache/meta/types.go
user: login: mariomac
body: Ii think you can avoid repeating the same code block if you just add another case to the condition in line 30:
```go
if (or.APIVersion == "apps/v1" && or.Kind == "ReplicaSet") ||
   (or.APIVersion == "batch/v1" && or.Kind == "Job") {
```
created_at: 2025-05-15T09:43:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,6 +36,15 @@ func ownersFrom(meta *metav1.ObjectMeta) []*informer.Owner {
 				return owners
 			}
 		}
+		if or.APIVersion == "batch/v1" && or.Kind == "Job" {
+			// we heuristically extract the CronJob name from the Job name
+			if idx := strings.LastIndexByte(or.Name, '-'); idx > 0 {
+				owners = append(owners, &informer.Owner{Kind: "CronJob", Name: or.Name[:idx]})
+				// we already have what we need for decoration and selection. Ignoring any other owner
+				// it might hypothetically have (it would be a rare case)
+				return owners
+			}
+		}
path: pkg/kubecache/meta/types.go
user: login: cuichenli
body: i don't know, but if we do that, we will have another nested condition for specify the owner kind 
```golang
if (or.APIVersion == "apps/v1" && or.Kind == "ReplicaSet") ||
   (or.APIVersion == "batch/v1" && or.Kind == "Job") {
	if or.Kind == "Job" {
		....
	} else if or.Kind == "ReplicaSet" {
		...
	}
```

personally i feel this will add unnecessary complexity, but if you think that is better, i can surely update accordingly. thanks!
created_at: 2025-05-15T09:51:10Z
author_association: MEMBER
diff_hunk: @@ -36,6 +36,15 @@ func ownersFrom(meta *metav1.ObjectMeta) []*informer.Owner {
 				return owners
 			}
 		}
+		if or.APIVersion == "batch/v1" && or.Kind == "Job" {
+			// we heuristically extract the CronJob name from the Job name
+			if idx := strings.LastIndexByte(or.Name, '-'); idx > 0 {
+				owners = append(owners, &informer.Owner{Kind: "CronJob", Name: or.Name[:idx]})
+				// we already have what we need for decoration and selection. Ignoring any other owner
+				// it might hypothetically have (it would be a rare case)
+				return owners
+			}
+		}
path: pkg/kubecache/meta/types.go
user: login: mariomac
body: Oh! you are right. I didn't notice the "subtle" difference. Then it's fine.
created_at: 2025-05-15T10:08:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-15T10:44:30Z
body: ## [Codecov](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 75.48%. Comparing base [(`d71df92`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/commit/d71df929f1102a0b1538e57fec4d615874471bb2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) to head [(`4afeb5a`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/commit/4afeb5a38ebfc2d38de964f19f9bd952d31708d3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main      #46      +/-   ##
==========================================
- Coverage   75.52%   75.48%   -0.04%     
==========================================
  Files         167      167              
  Lines       17589    17605      +16     
==========================================
+ Hits        13284    13290       +6     
- Misses       3615     3623       +8     
- Partials      690      692       +2     
```

| [Flag](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [integration-test](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `55.70% <11.76%> (-0.04%)` | :arrow_down: |
| [integration-test-arm](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `34.76% <11.76%> (-0.05%)` | :arrow_down: |
| [k8s-integration-test](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `54.02% <100.00%> (-0.02%)` | :arrow_down: |
| [oats-test](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `37.65% <11.76%> (+0.11%)` | :arrow_up: |
| [unittests](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `46.29% <11.76%> (-0.04%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/open-telemetry/opentelemetry-ebpf-instrumentation/pull/46?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: cuichenli
created_at: 2025-05-15T11:33:40Z
body: looks like one integration test is failing. but i checked some other prs and this failure also occured. is that a flaky test?
author_association: MEMBER
user: login: grcevski
created_at: 2025-05-15T14:58:33Z
body: > looks like one integration test is failing. but i checked some other prs and this failure also occured. is that a flaky test?

It's a flaky test, I've kicked off a re-run.
author_association: CONTRIBUTOR
user: login: cuichenli
created_at: 2025-05-16T04:26:22Z
body: the previous code actually break one integration test, i have fixed it locally. but still i got failure on `TestMultiNodeTracingL7`. but i think this one is not related. lets see how it goes in the ci, i will try to fix this flaky test
author_association: MEMBER
user: login: cuichenli
created_at: 2025-05-16T05:17:37Z
body: just realized the `TestMultiNodeTracingL7` has been removed in the main branch. but i am still failing at `TestMultiNodeTracing` locally. 
author_association: MEMBER