Skip to content
Merged
3 changes: 3 additions & 0 deletions pkg/beyla/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ network:
services.GlobAttributes{
Metadata: map[string]*services.GlobAttr{"k8s_namespace": &servicesextra.K8sDefaultNamespacesGlob},
},
services.GlobAttributes{
Metadata: map[string]*services.GlobAttr{"k8s_container_name": &servicesextra.K8sDefaultExcludeContainerNamesGlob},
},
},
DefaultOtlpGRPCPort: 4317,
RouteHarvesterTimeout: 10 * time.Second,
Expand Down
7 changes: 7 additions & 0 deletions pkg/services/criteria.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var K8sDefaultNamespacesGlob = services.NewGlob("{kube-system,kube-node-lease,lo

var K8sDefaultNamespacesWithSurveyRegex = services.NewRegexp("^kube-system$|^kube-node-lease$|^local-path-storage$|^cert-manager$" + k8sGKEDefaultNamespacesRegex + k8sAKSDefaultNamespacesRegex)
var K8sDefaultNamespacesWithSurveyGlob = services.NewGlob("{kube-system,kube-node-lease,local-path-storage,cert-manager" + k8sGKEDefaultNamespacesGlob + k8sAKSDefaultNamespacesGlob + "}")
var K8sDefaultExcludeContainerNamesGlob = services.NewGlob("{beyla,ebpf-instrument,alloy,prometheus-config-reloader,otelcol,otelcol-contrib}")

var DefaultExcludeServices = services.RegexDefinitionCriteria{
services.RegexSelector{
Expand All @@ -47,6 +48,9 @@ var DefaultExcludeInstrument = services.GlobDefinitionCriteria{
services.GlobAttributes{
Metadata: map[string]*services.GlobAttr{"k8s_namespace": &K8sDefaultNamespacesGlob},
},
services.GlobAttributes{
Metadata: map[string]*services.GlobAttr{"k8s_container_name": &K8sDefaultExcludeContainerNamesGlob},
},
}
var DefaultExcludeInstrumentWithSurvey = services.GlobDefinitionCriteria{
services.GlobAttributes{
Expand All @@ -55,6 +59,9 @@ var DefaultExcludeInstrumentWithSurvey = services.GlobDefinitionCriteria{
services.GlobAttributes{
Metadata: map[string]*services.GlobAttr{"k8s_namespace": &K8sDefaultNamespacesWithSurveyGlob},
},
services.GlobAttributes{
Metadata: map[string]*services.GlobAttr{"k8s_container_name": &K8sDefaultExcludeContainerNamesGlob},
},
}

// DiscoveryConfig for the discover.ProcessFinder pipeline
Expand Down
215 changes: 215 additions & 0 deletions scripts/check-obi-drift.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#!/usr/bin/env bash
# Detect and optionally sync test functions that have drifted between Beyla and OBI
#
# Usage:
# ./scripts/check-obi-drift.sh # Check for drift (exit 1 if found)
# ./scripts/check-obi-drift.sh --sync # Apply OBI changes to Beyla files
# ./scripts/check-obi-drift.sh --help # Show help

set -euo pipefail

OBI_DIR=".obi-src/test/integration"
BEYLA_DIR="test/integration"
SYNC_MODE=false

# Find test functions that exist in both Beyla and OBI
find_common_functions() {
# Get function names from both directories
local beyla_funcs=$(grep -rh "^func test[a-zA-Z0-9_]*(" "$BEYLA_DIR"/*.go 2>/dev/null | \
sed 's/^func \([a-zA-Z0-9_]*\).*/\1/' | sort -u)

local obi_funcs=$(grep -rh "^func test[a-zA-Z0-9_]*(" "$OBI_DIR"/*.go 2>/dev/null | \
sed 's/^func \([a-zA-Z0-9_]*\).*/\1/' | sort -u)

# Find common functions
comm -12 <(echo "$beyla_funcs") <(echo "$obi_funcs")
}

# Extract function body from a file
extract_function() {
local func="$1"
local file="$2"

# Extract function from start to closing brace at column 0
# Use \( to match the opening parenthesis and avoid matching function name prefixes
sed -n "/^func $func(/,/^}/p" "$file"
}

# Find which file contains a function in a directory
find_function_file() {
local func="$1"
local dir="$2"

grep -l "^func $func(" "$dir"/*.go 2>/dev/null | head -1
}

# Sync function from OBI to Beyla
sync_function() {
local func="$1"
local beyla_file="$2"
local obi_file="$3"

echo " Syncing $func from OBI..."

# Create temp file with transformed function
local temp_func=$(mktemp)

# Extract the OBI function body and transform for Beyla
extract_function "$func" "$obi_file" | \
sed 's|go.opentelemetry.io/obi|github.com/grafana/beyla|g' | \
sed 's|obi_|beyla_|g' | \
sed 's|service_name="opentelemetry-ebpf-instrumentation"|service_name="beyla"|g' | \
sed 's|telemetry.sdk.name", Type: "string", Value: "opentelemetry-ebpf-instrumentation"|telemetry.sdk.name", Type: "string", Value: "beyla"|g' \
> "$temp_func"

# Use go run with flags (avoids *_test.go file path parsing issues)
local script_dir="$(dirname "$0")"
go run "$script_dir/replace-function.go" -file "$beyla_file" -func "$func" -new "$temp_func"

# Clean up
rm -f "$temp_func"

echo " ✓ Synced $func in ${beyla_file##*/}"
}

# Compare function implementations
check_drift() {
local func="$1"

# Find files containing this function
local beyla_file=$(find_function_file "$func" "$BEYLA_DIR")
local obi_file=$(find_function_file "$func" "$OBI_DIR")

if [[ -z "$beyla_file" ]] || [[ -z "$obi_file" ]]; then
return 0 # Skip if not found in both
fi

# Extract function bodies
local beyla_body=$(extract_function "$func" "$beyla_file")
local obi_body=$(extract_function "$func" "$obi_file")

# Normalize to ignore copyright, import path, and metric name changes
local beyla_normalized=$(echo "$beyla_body" | \
grep -v "Copyright The OpenTelemetry Authors" | \
grep -v "SPDX-License-Identifier: Apache-2.0" | \
sed 's|github.com/grafana/beyla|go.opentelemetry.io/obi|g' | \
sed 's|beyla_|obi_|g' | \
sed 's|service_name="beyla"|service_name="opentelemetry-ebpf-instrumentation"|g' | \
sed 's|telemetry.sdk.name", Type: "string", Value: "beyla"|telemetry.sdk.name", Type: "string", Value: "opentelemetry-ebpf-instrumentation"|g')
local obi_normalized=$(echo "$obi_body" | \
grep -v "Copyright The OpenTelemetry Authors" | \
grep -v "SPDX-License-Identifier: Apache-2.0")

if [[ "$beyla_normalized" != "$obi_normalized" ]]; then
echo "$func"
echo " Beyla: ${beyla_file##*/}"
echo " OBI: ${obi_file##*/}"
echo ""

if [[ "$SYNC_MODE" == "true" ]]; then
sync_function "$func" "$beyla_file" "$obi_file"
else
# Show color-coded diff using normalized versions
diff -u \
<(echo "$beyla_normalized") \
<(echo "$obi_normalized") 2>/dev/null | \
sed 's/^-/\x1b[31m-/; s/^+/\x1b[32m+/; s/^@/\x1b[36m@/; s/$/\x1b[0m/' || true
fi

echo ""
return 1
fi

return 0
}

# Show help
show_help() {
cat << EOF
Usage: $0 [OPTIONS]

Check for drift between Beyla and OBI test functions, and optionally sync them.

Options:
--sync Apply OBI changes to Beyla files
--help Show this help message

Examples:
# Check for drift (exit 1 if any found)
$0

# Apply OBI changes to Beyla files
$0 --sync

When --sync is used:
- Replaces drifted Beyla functions with OBI versions
- Creates a git-ready changeset
- You can review changes with 'git diff' and create a PR

EOF
}

# Main
main() {
if [[ "$SYNC_MODE" == "true" ]]; then
echo "Syncing drifted test functions from OBI to Beyla..."
else
echo "Checking for drift between Beyla and OBI test functions..."
fi
echo ""

local common_funcs=$(find_common_functions)
local total=0
local drifted=0

for func in $common_funcs; do
((total++))
if ! check_drift "$func"; then
((drifted++))
fi
done

echo "Summary: $drifted/$total test functions have drifted from OBI"

if [[ $drifted -gt 0 ]]; then
echo ""
if [[ "$SYNC_MODE" == "true" ]]; then
echo "✓ Synced $drifted functions from OBI"
echo ""
echo "Next steps:"
echo " 1. Review changes: git diff test/integration/"
echo " 2. Run tests: go test -tags=integration ./test/integration/..."
echo " 3. Create PR: git add test/integration/ && git commit -m 'Sync tests from OBI'"
else
echo "To sync these changes automatically:"
echo " $0 --sync"
echo ""
echo "To see full diff for a function:"
echo " diff -u test/integration/traces_test.go .obi-src/test/integration/traces_test.go"
exit 1
fi
fi
}

# Parse arguments
case "${1:-}" in
--sync)
SYNC_MODE=true
;;
--help)
show_help
exit 0
;;
"")
# Default: check mode
;;
*)
echo "Error: Unknown option: $1"
echo ""
show_help
exit 1
;;
esac

main

122 changes: 122 additions & 0 deletions scripts/replace-function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"bytes"
"flag"
"fmt"
"os"
"regexp"
)

// findFunction finds the start and end byte positions of a function in source code
func findFunction(content []byte, funcName string) (start, end int, err error) {
// Find function declaration
funcPattern := fmt.Sprintf(`(?m)^func %s\(`, regexp.QuoteMeta(funcName))
re := regexp.MustCompile(funcPattern)

loc := re.FindIndex(content)
if loc == nil {
return 0, 0, fmt.Errorf("function %s not found", funcName)
}

start = loc[0]

// Find the opening brace
openBrace := -1
for i := loc[1]; i < len(content); i++ {
if content[i] == '{' {
openBrace = i
break
}
}

if openBrace == -1 {
return 0, 0, fmt.Errorf("opening brace not found for function %s", funcName)
}

// Count braces to find matching closing brace
braceCount := 1
for i := openBrace + 1; i < len(content); i++ {
switch content[i] {
case '{':
braceCount++
case '}':
braceCount--
if braceCount == 0 {
// Found the matching closing brace
// Include the closing brace and newline if present
end = i + 1
if end < len(content) && content[end] == '\n' {
end++
}
return start, end, nil
}
}
}

return 0, 0, fmt.Errorf("matching closing brace not found for function %s", funcName)
}

// replaceFunction replaces a function in a Go file with new content, preserving exact formatting
func replaceFunction(filepath, funcName, newFuncContent string) error {
// Read the file
content, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}

// Find the function to replace
start, end, err := findFunction(content, funcName)
if err != nil {
return err
}

// Build new content: before + new function + after
var buf bytes.Buffer
buf.Write(content[:start])
buf.WriteString(newFuncContent)
if end < len(content) {
buf.Write(content[end:])
}

// Write back to file
return os.WriteFile(filepath, buf.Bytes(), 0644)
}

func main() {
var (
filepath string
funcName string
newFuncFile string
)

flag := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
flag.StringVar(&filepath, "file", "", "Go source file to modify")
flag.StringVar(&funcName, "func", "", "Function name to replace")
flag.StringVar(&newFuncFile, "new", "", "File containing new function content")

if err := flag.Parse(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err)
os.Exit(1)
}

if filepath == "" || funcName == "" || newFuncFile == "" {
fmt.Fprintf(os.Stderr, "Usage: %s -file <file> -func <function_name> -new <new_function_file>\n", os.Args[0])
flag.PrintDefaults()
os.Exit(1)
}

// Read new function content
newFuncContent, err := os.ReadFile(newFuncFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading new function file: %v\n", err)
os.Exit(1)
}

if err := replaceFunction(filepath, funcName, string(newFuncContent)); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

fmt.Printf("✓ Replaced %s in %s\n", funcName, filepath)
}
Loading
Loading