Somewhat okay refactoring
This commit is contained in:
921
internal/ebpf/ebpf_trace_test.go
Normal file
921
internal/ebpf/ebpf_trace_test.go
Normal file
@@ -0,0 +1,921 @@
|
||||
package ebpf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestBCCTracing demonstrates and tests the new BCC-style tracing functionality
|
||||
// This test documents the expected behavior and response format of the agent
|
||||
func TestBCCTracing(t *testing.T) {
|
||||
fmt.Println("=== BCC-Style eBPF Tracing Unit Tests ===")
|
||||
fmt.Println()
|
||||
|
||||
// Test 1: List available test specifications
|
||||
t.Run("ListTestSpecs", func(t *testing.T) {
|
||||
specs := ListTestSpecs()
|
||||
fmt.Printf("📋 Available Test Specifications:\n")
|
||||
for name, description := range specs {
|
||||
fmt.Printf(" - %s: %s\n", name, description)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if len(specs) == 0 {
|
||||
t.Error("No test specifications available")
|
||||
}
|
||||
})
|
||||
|
||||
// Test 2: Parse BCC-style specifications
|
||||
t.Run("ParseBCCStyle", func(t *testing.T) {
|
||||
parser := NewTraceSpecParser()
|
||||
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "sys_open",
|
||||
expected: "__x64_sys_open",
|
||||
},
|
||||
{
|
||||
input: "p::do_sys_open",
|
||||
expected: "do_sys_open",
|
||||
},
|
||||
{
|
||||
input: "r::sys_read",
|
||||
expected: "sys_read",
|
||||
},
|
||||
{
|
||||
input: "sys_write (arg1 == 1)",
|
||||
expected: "__x64_sys_write",
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("🔍 Testing BCC-style parsing:\n")
|
||||
for _, tc := range testCases {
|
||||
spec, err := parser.ParseFromBCCStyle(tc.input)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse '%s': %v", tc.input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" Input: '%s' -> Target: '%s', Type: '%s'\n",
|
||||
tc.input, spec.Target, spec.ProbeType)
|
||||
|
||||
if spec.Target != tc.expected {
|
||||
t.Errorf("Expected target '%s', got '%s'", tc.expected, spec.Target)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
})
|
||||
|
||||
// Test 3: Validate trace specifications
|
||||
t.Run("ValidateSpecs", func(t *testing.T) {
|
||||
fmt.Printf("✅ Testing trace specification validation:\n")
|
||||
|
||||
// Valid spec
|
||||
validSpec := TraceSpec{
|
||||
ProbeType: "p",
|
||||
Target: "__x64_sys_openat",
|
||||
Format: "opening file",
|
||||
Duration: 5,
|
||||
}
|
||||
|
||||
if err := ValidateTraceSpec(validSpec); err != nil {
|
||||
t.Errorf("Valid spec failed validation: %v", err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ Valid specification passed\n")
|
||||
}
|
||||
|
||||
// Invalid spec - no target
|
||||
invalidSpec := TraceSpec{
|
||||
ProbeType: "p",
|
||||
Duration: 5,
|
||||
}
|
||||
|
||||
if err := ValidateTraceSpec(invalidSpec); err == nil {
|
||||
t.Error("Invalid spec (no target) should have failed validation")
|
||||
} else {
|
||||
fmt.Printf(" ✓ Invalid specification correctly rejected: %s\n", err.Error())
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
})
|
||||
|
||||
// Test 4: Simulate agent response format
|
||||
t.Run("SimulateAgentResponse", func(t *testing.T) {
|
||||
fmt.Printf("🤖 Simulating agent response for BCC-style tracing:\n")
|
||||
|
||||
// Get a test specification
|
||||
testSpec, exists := GetTestSpec("test_sys_open")
|
||||
if !exists {
|
||||
t.Fatal("test_sys_open specification not found")
|
||||
}
|
||||
|
||||
// Simulate what the agent would return
|
||||
mockResponse := simulateTraceExecution(testSpec)
|
||||
|
||||
// Print the response format
|
||||
responseJSON, _ := json.MarshalIndent(mockResponse, "", " ")
|
||||
fmt.Printf(" Expected Response Format:\n%s\n", string(responseJSON))
|
||||
|
||||
// Validate response structure
|
||||
if mockResponse["success"] != true {
|
||||
t.Error("Expected successful trace execution")
|
||||
}
|
||||
|
||||
if mockResponse["type"] != "bcc_trace" {
|
||||
t.Error("Expected type to be 'bcc_trace'")
|
||||
}
|
||||
|
||||
events, hasEvents := mockResponse["events"].([]TraceEvent)
|
||||
if !hasEvents || len(events) == 0 {
|
||||
t.Error("Expected trace events in response")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
})
|
||||
|
||||
// Test 5: Test different probe types
|
||||
t.Run("TestProbeTypes", func(t *testing.T) {
|
||||
fmt.Printf("🔬 Testing different probe types:\n")
|
||||
|
||||
probeTests := []struct {
|
||||
specName string
|
||||
expected string
|
||||
}{
|
||||
{"test_sys_open", "kprobe"},
|
||||
{"test_kretprobe", "kretprobe"},
|
||||
{"test_with_filter", "kprobe with filter"},
|
||||
}
|
||||
|
||||
for _, test := range probeTests {
|
||||
spec, exists := GetTestSpec(test.specName)
|
||||
if !exists {
|
||||
t.Errorf("Test spec '%s' not found", test.specName)
|
||||
continue
|
||||
}
|
||||
|
||||
response := simulateTraceExecution(spec)
|
||||
fmt.Printf(" %s -> %s: %d events captured\n",
|
||||
test.specName, test.expected, response["event_count"])
|
||||
}
|
||||
fmt.Println()
|
||||
})
|
||||
|
||||
// Test 6: Test trace spec builder
|
||||
t.Run("TestTraceSpecBuilder", func(t *testing.T) {
|
||||
fmt.Printf("🏗️ Testing trace specification builder:\n")
|
||||
|
||||
// Build a custom trace spec
|
||||
spec := NewTraceSpecBuilder().
|
||||
Kprobe("__x64_sys_write").
|
||||
Format("write syscall: %d bytes", "arg3").
|
||||
Filter("arg1 == 1").
|
||||
Duration(3).
|
||||
Build()
|
||||
|
||||
fmt.Printf(" Built spec: Target=%s, Format=%s, Filter=%s\n",
|
||||
spec.Target, spec.Format, spec.Filter)
|
||||
|
||||
if spec.Target != "__x64_sys_write" {
|
||||
t.Error("Builder failed to set target correctly")
|
||||
}
|
||||
|
||||
if spec.ProbeType != "p" {
|
||||
t.Error("Builder failed to set probe type correctly")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
})
|
||||
}
|
||||
|
||||
// simulateTraceExecution simulates what the agent would return for a trace execution
|
||||
// This documents the expected response format from the agent
|
||||
func simulateTraceExecution(spec TraceSpec) map[string]interface{} {
|
||||
// Simulate some trace events
|
||||
events := []TraceEvent{
|
||||
{
|
||||
Timestamp: time.Now().Unix(),
|
||||
PID: 1234,
|
||||
TID: 1234,
|
||||
ProcessName: "test_process",
|
||||
Function: spec.Target,
|
||||
Message: fmt.Sprintf(spec.Format, "test_file.txt"),
|
||||
RawArgs: map[string]string{
|
||||
"arg1": "5",
|
||||
"arg2": "test_file.txt",
|
||||
"arg3": "1024",
|
||||
},
|
||||
},
|
||||
{
|
||||
Timestamp: time.Now().Unix(),
|
||||
PID: 5678,
|
||||
TID: 5678,
|
||||
ProcessName: "another_process",
|
||||
Function: spec.Target,
|
||||
Message: fmt.Sprintf(spec.Format, "data.log"),
|
||||
RawArgs: map[string]string{
|
||||
"arg1": "3",
|
||||
"arg2": "data.log",
|
||||
"arg3": "512",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Simulate trace statistics
|
||||
stats := TraceStats{
|
||||
TotalEvents: len(events),
|
||||
EventsByProcess: map[string]int{"test_process": 1, "another_process": 1},
|
||||
EventsByUID: map[int]int{1000: 2},
|
||||
EventsPerSecond: float64(len(events)) / float64(spec.Duration),
|
||||
TopProcesses: []ProcessStat{
|
||||
{ProcessName: "test_process", EventCount: 1, Percentage: 50.0},
|
||||
{ProcessName: "another_process", EventCount: 1, Percentage: 50.0},
|
||||
},
|
||||
}
|
||||
|
||||
// Return the expected agent response format
|
||||
return map[string]interface{}{
|
||||
"name": spec.Target,
|
||||
"type": "bcc_trace",
|
||||
"target": spec.Target,
|
||||
"duration": spec.Duration,
|
||||
"description": fmt.Sprintf("Traced %s for %d seconds", spec.Target, spec.Duration),
|
||||
"status": "completed",
|
||||
"success": true,
|
||||
"event_count": len(events),
|
||||
"events": events,
|
||||
"statistics": stats,
|
||||
"data_points": len(events),
|
||||
"probe_type": spec.ProbeType,
|
||||
"format": spec.Format,
|
||||
"filter": spec.Filter,
|
||||
}
|
||||
}
|
||||
|
||||
// TestTraceManagerCapabilities tests the trace manager capabilities
|
||||
func TestTraceManagerCapabilities(t *testing.T) {
|
||||
fmt.Println("=== BCC Trace Manager Capabilities Test ===")
|
||||
fmt.Println()
|
||||
|
||||
manager := NewBCCTraceManager()
|
||||
caps := manager.GetCapabilities()
|
||||
|
||||
fmt.Printf("🔧 Trace Manager Capabilities:\n")
|
||||
for capability, available := range caps {
|
||||
status := "❌ Not Available"
|
||||
if available {
|
||||
status = "✅ Available"
|
||||
}
|
||||
fmt.Printf(" %s: %s\n", capability, status)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Check essential capabilities
|
||||
if !caps["kernel_ebpf"] {
|
||||
fmt.Printf("⚠️ Warning: Kernel eBPF support not detected\n")
|
||||
}
|
||||
|
||||
if !caps["bpftrace"] {
|
||||
fmt.Printf("⚠️ Warning: bpftrace not available (install with: apt install bpftrace)\n")
|
||||
}
|
||||
|
||||
if !caps["root_access"] {
|
||||
fmt.Printf("⚠️ Warning: Root access required for eBPF tracing\n")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTraceSpecParsing benchmarks the trace specification parsing
|
||||
func BenchmarkTraceSpecParsing(b *testing.B) {
|
||||
parser := NewTraceSpecParser()
|
||||
testInput := "sys_open \"opening %s\", arg2@user"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseFromBCCStyle(testInput)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyscallSuggestions tests the syscall suggestion functionality
|
||||
func TestSyscallSuggestions(t *testing.T) {
|
||||
fmt.Println("=== Syscall Suggestion Test ===")
|
||||
fmt.Println()
|
||||
|
||||
testCases := []struct {
|
||||
issue string
|
||||
expected int // minimum expected suggestions
|
||||
description string
|
||||
}{
|
||||
{
|
||||
issue: "file not found error",
|
||||
expected: 1,
|
||||
description: "File I/O issue should suggest file-related syscalls",
|
||||
},
|
||||
{
|
||||
issue: "network connection timeout",
|
||||
expected: 1,
|
||||
description: "Network issue should suggest network syscalls",
|
||||
},
|
||||
{
|
||||
issue: "process crashes randomly",
|
||||
expected: 1,
|
||||
description: "Process issue should suggest process-related syscalls",
|
||||
},
|
||||
{
|
||||
issue: "memory leak detected",
|
||||
expected: 1,
|
||||
description: "Memory issue should suggest memory syscalls",
|
||||
},
|
||||
{
|
||||
issue: "application is slow",
|
||||
expected: 1,
|
||||
description: "Performance issue should suggest monitoring syscalls",
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("💡 Testing syscall suggestions:\n")
|
||||
for _, tc := range testCases {
|
||||
suggestions := SuggestSyscallTargets(tc.issue)
|
||||
fmt.Printf(" Issue: '%s' -> %d suggestions: %v\n",
|
||||
tc.issue, len(suggestions), suggestions)
|
||||
|
||||
if len(suggestions) < tc.expected {
|
||||
t.Errorf("Expected at least %d suggestions for '%s', got %d",
|
||||
tc.expected, tc.issue, len(suggestions))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// TestMain runs the tests and provides a summary
|
||||
func TestMain(m *testing.M) {
|
||||
fmt.Println("🚀 Starting BCC-Style eBPF Tracing Tests")
|
||||
fmt.Println("========================================")
|
||||
fmt.Println()
|
||||
|
||||
// Run capability check first
|
||||
manager := NewBCCTraceManager()
|
||||
caps := manager.GetCapabilities()
|
||||
|
||||
if !caps["kernel_ebpf"] {
|
||||
fmt.Println("⚠️ Kernel eBPF support not detected - some tests may be limited")
|
||||
}
|
||||
if !caps["bpftrace"] {
|
||||
fmt.Println("⚠️ bpftrace not available - install with: sudo apt install bpftrace")
|
||||
}
|
||||
if !caps["root_access"] {
|
||||
fmt.Println("⚠️ Root access required for actual eBPF tracing")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Run the tests
|
||||
code := m.Run()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("========================================")
|
||||
if code == 0 {
|
||||
fmt.Println("✅ All BCC-Style eBPF Tracing Tests Passed!")
|
||||
} else {
|
||||
fmt.Println("❌ Some tests failed")
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// TestBCCTraceManagerRootTest tests the actual BCC trace manager with root privileges
|
||||
// This test requires root access and will only run meaningful tests when root
|
||||
func TestBCCTraceManagerRootTest(t *testing.T) {
|
||||
fmt.Println("=== BCC Trace Manager Root Test ===")
|
||||
|
||||
// Check if running as root
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("⚠️ Skipping root test - not running as root (use: sudo go test -run TestBCCTraceManagerRootTest)")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("✅ Running as root - can test actual eBPF functionality")
|
||||
|
||||
// Test 1: Create BCC trace manager and check capabilities
|
||||
manager := NewBCCTraceManager()
|
||||
caps := manager.GetCapabilities()
|
||||
|
||||
fmt.Printf("🔍 BCC Trace Manager Capabilities:\n")
|
||||
for cap, available := range caps {
|
||||
status := "❌"
|
||||
if available {
|
||||
status = "✅"
|
||||
}
|
||||
fmt.Printf(" %s %s: %v\n", status, cap, available)
|
||||
}
|
||||
|
||||
// Require essential capabilities
|
||||
if !caps["bpftrace"] {
|
||||
t.Fatal("❌ bpftrace not available - install bpftrace package")
|
||||
}
|
||||
|
||||
if !caps["root_access"] {
|
||||
t.Fatal("❌ Root access not detected")
|
||||
}
|
||||
|
||||
// Test 2: Create and execute a simple trace
|
||||
fmt.Println("\n🔬 Testing actual eBPF trace execution...")
|
||||
|
||||
spec := TraceSpec{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "syscalls:sys_enter_openat",
|
||||
Format: "file access",
|
||||
Arguments: []string{}, // Remove invalid arg2@user for tracepoints
|
||||
Duration: 3, // 3 seconds
|
||||
}
|
||||
|
||||
fmt.Printf("📝 Starting trace: %s for %d seconds\n", spec.Target, spec.Duration)
|
||||
|
||||
traceID, err := manager.StartTrace(spec)
|
||||
if err != nil {
|
||||
t.Fatalf("❌ Failed to start trace: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("🚀 Trace started with ID: %s\n", traceID)
|
||||
|
||||
// Generate some file access to capture
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
// Create some file operations to trace
|
||||
for i := 0; i < 3; i++ {
|
||||
testFile := fmt.Sprintf("/tmp/bcc_test_%d.txt", i)
|
||||
|
||||
// This will trigger sys_openat syscalls
|
||||
if file, err := os.Create(testFile); err == nil {
|
||||
file.WriteString("BCC trace test")
|
||||
file.Close()
|
||||
os.Remove(testFile)
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for trace to complete
|
||||
time.Sleep(time.Duration(spec.Duration+1) * time.Second)
|
||||
|
||||
// Get results
|
||||
result, err := manager.GetTraceResult(traceID)
|
||||
if err != nil {
|
||||
// Try to stop the trace if it's still running
|
||||
manager.StopTrace(traceID)
|
||||
t.Fatalf("❌ Failed to get trace results: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n📊 Trace Results Summary:\n")
|
||||
fmt.Printf(" • Trace ID: %s\n", result.TraceID)
|
||||
fmt.Printf(" • Target: %s\n", result.Spec.Target)
|
||||
fmt.Printf(" • Duration: %v\n", result.EndTime.Sub(result.StartTime))
|
||||
fmt.Printf(" • Events captured: %d\n", result.EventCount)
|
||||
fmt.Printf(" • Events per second: %.2f\n", result.Statistics.EventsPerSecond)
|
||||
fmt.Printf(" • Summary: %s\n", result.Summary)
|
||||
|
||||
if len(result.Events) > 0 {
|
||||
fmt.Printf("\n📝 Sample Events (first 3):\n")
|
||||
for i, event := range result.Events {
|
||||
if i >= 3 {
|
||||
break
|
||||
}
|
||||
fmt.Printf(" %d. PID:%d TID:%d Process:%s Message:%s\n",
|
||||
i+1, event.PID, event.TID, event.ProcessName, event.Message)
|
||||
}
|
||||
|
||||
if len(result.Events) > 3 {
|
||||
fmt.Printf(" ... and %d more events\n", len(result.Events)-3)
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: Validate the trace produced real data
|
||||
if result.EventCount == 0 {
|
||||
fmt.Println("⚠️ Warning: No events captured - this might be normal for a quiet system")
|
||||
} else {
|
||||
fmt.Printf("✅ Successfully captured %d real eBPF events!\n", result.EventCount)
|
||||
}
|
||||
|
||||
fmt.Println("\n🧪 Testing comprehensive system tracing (Network, Disk, CPU, Memory, Userspace)...")
|
||||
|
||||
testSpecs := []TraceSpec{
|
||||
// === SYSCALL TRACING ===
|
||||
{
|
||||
ProbeType: "p", // kprobe
|
||||
Target: "__x64_sys_write",
|
||||
Format: "write: fd=%d count=%d",
|
||||
Arguments: []string{"arg1", "arg3"},
|
||||
Duration: 2,
|
||||
},
|
||||
{
|
||||
ProbeType: "p", // kprobe
|
||||
Target: "__x64_sys_read",
|
||||
Format: "read: fd=%d count=%d",
|
||||
Arguments: []string{"arg1", "arg3"},
|
||||
Duration: 2,
|
||||
},
|
||||
{
|
||||
ProbeType: "p", // kprobe
|
||||
Target: "__x64_sys_connect",
|
||||
Format: "network connect: fd=%d",
|
||||
Arguments: []string{"arg1"},
|
||||
Duration: 2,
|
||||
},
|
||||
{
|
||||
ProbeType: "p", // kprobe
|
||||
Target: "__x64_sys_accept",
|
||||
Format: "network accept: fd=%d",
|
||||
Arguments: []string{"arg1"},
|
||||
Duration: 2,
|
||||
},
|
||||
// === BLOCK I/O TRACING ===
|
||||
{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "block:block_io_start",
|
||||
Format: "block I/O start",
|
||||
Arguments: []string{},
|
||||
Duration: 2,
|
||||
},
|
||||
{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "block:block_io_done",
|
||||
Format: "block I/O complete",
|
||||
Arguments: []string{},
|
||||
Duration: 2,
|
||||
},
|
||||
// === CPU SCHEDULER TRACING ===
|
||||
{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "sched:sched_migrate_task",
|
||||
Format: "task migration",
|
||||
Arguments: []string{},
|
||||
Duration: 2,
|
||||
},
|
||||
{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "sched:sched_pi_setprio",
|
||||
Format: "priority change",
|
||||
Arguments: []string{},
|
||||
Duration: 2,
|
||||
},
|
||||
// === MEMORY MANAGEMENT ===
|
||||
{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "syscalls:sys_enter_brk",
|
||||
Format: "memory allocation: brk",
|
||||
Arguments: []string{},
|
||||
Duration: 2,
|
||||
},
|
||||
// === KERNEL MEMORY TRACING ===
|
||||
{
|
||||
ProbeType: "t", // tracepoint
|
||||
Target: "kmem:kfree",
|
||||
Format: "kernel memory free",
|
||||
Arguments: []string{},
|
||||
Duration: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testSpec := range testSpecs {
|
||||
category := "unknown"
|
||||
if strings.Contains(testSpec.Target, "sys_write") || strings.Contains(testSpec.Target, "sys_read") {
|
||||
category = "filesystem"
|
||||
} else if strings.Contains(testSpec.Target, "sys_connect") || strings.Contains(testSpec.Target, "sys_accept") {
|
||||
category = "network"
|
||||
} else if strings.Contains(testSpec.Target, "block:") {
|
||||
category = "disk I/O"
|
||||
} else if strings.Contains(testSpec.Target, "sched:") {
|
||||
category = "CPU/scheduler"
|
||||
} else if strings.Contains(testSpec.Target, "sys_brk") || strings.Contains(testSpec.Target, "kmem:") {
|
||||
category = "memory"
|
||||
}
|
||||
|
||||
fmt.Printf("\n 🔍 Test %d: [%s] Tracing %s for %d seconds\n", i+1, category, testSpec.Target, testSpec.Duration)
|
||||
|
||||
testTraceID, err := manager.StartTrace(testSpec)
|
||||
if err != nil {
|
||||
fmt.Printf(" ❌ Failed to start: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate activity specific to this trace type
|
||||
go func(target, probeType string) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
switch {
|
||||
case strings.Contains(target, "sys_write") || strings.Contains(target, "sys_read"):
|
||||
// Generate file I/O
|
||||
for j := 0; j < 3; j++ {
|
||||
testFile := fmt.Sprintf("/tmp/io_test_%d.txt", j)
|
||||
if file, err := os.Create(testFile); err == nil {
|
||||
file.WriteString("BCC tracing test data for I/O operations")
|
||||
file.Sync()
|
||||
file.Close()
|
||||
|
||||
// Read the file back
|
||||
if readFile, err := os.Open(testFile); err == nil {
|
||||
buffer := make([]byte, 1024)
|
||||
readFile.Read(buffer)
|
||||
readFile.Close()
|
||||
}
|
||||
os.Remove(testFile)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
case strings.Contains(target, "block:"):
|
||||
// Generate disk I/O to trigger block layer events
|
||||
for j := 0; j < 3; j++ {
|
||||
testFile := fmt.Sprintf("/tmp/block_test_%d.txt", j)
|
||||
if file, err := os.Create(testFile); err == nil {
|
||||
// Write substantial data to trigger block I/O
|
||||
data := make([]byte, 1024*4) // 4KB
|
||||
for k := range data {
|
||||
data[k] = byte(k % 256)
|
||||
}
|
||||
file.Write(data)
|
||||
file.Sync() // Force write to disk
|
||||
file.Close()
|
||||
}
|
||||
os.Remove(testFile)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
case strings.Contains(target, "sched:"):
|
||||
// Generate CPU activity to trigger scheduler events
|
||||
go func() {
|
||||
for j := 0; j < 100; j++ {
|
||||
// Create short-lived goroutines to trigger scheduler activity
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 1)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}()
|
||||
case strings.Contains(target, "sys_brk") || strings.Contains(target, "kmem:"):
|
||||
// Generate memory allocation activity
|
||||
for j := 0; j < 5; j++ {
|
||||
// Allocate and free memory to trigger memory management
|
||||
data := make([]byte, 1024*1024) // 1MB
|
||||
for k := range data {
|
||||
data[k] = byte(k % 256)
|
||||
}
|
||||
data = nil // Allow GC
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
case strings.Contains(target, "sys_connect") || strings.Contains(target, "sys_accept"):
|
||||
// Network operations (these may not generate events in test environment)
|
||||
fmt.Printf(" Note: Network syscalls may not trigger events without actual network activity\n")
|
||||
default:
|
||||
// Generic activity
|
||||
for j := 0; j < 3; j++ {
|
||||
testFile := fmt.Sprintf("/tmp/generic_test_%d.txt", j)
|
||||
if file, err := os.Create(testFile); err == nil {
|
||||
file.WriteString("Generic test activity")
|
||||
file.Close()
|
||||
}
|
||||
os.Remove(testFile)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}(testSpec.Target, testSpec.ProbeType)
|
||||
|
||||
// Wait for trace completion
|
||||
time.Sleep(time.Duration(testSpec.Duration+1) * time.Second)
|
||||
|
||||
testResult, err := manager.GetTraceResult(testTraceID)
|
||||
if err != nil {
|
||||
manager.StopTrace(testTraceID)
|
||||
fmt.Printf(" ⚠️ Result error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" 📊 Results for %s:\n", testSpec.Target)
|
||||
fmt.Printf(" • Total events: %d\n", testResult.EventCount)
|
||||
fmt.Printf(" • Events/sec: %.2f\n", testResult.Statistics.EventsPerSecond)
|
||||
fmt.Printf(" • Duration: %v\n", testResult.EndTime.Sub(testResult.StartTime))
|
||||
|
||||
// Show process breakdown
|
||||
if len(testResult.Statistics.TopProcesses) > 0 {
|
||||
fmt.Printf(" • Top processes:\n")
|
||||
for j, proc := range testResult.Statistics.TopProcesses {
|
||||
if j >= 3 { // Show top 3
|
||||
break
|
||||
}
|
||||
fmt.Printf(" - %s: %d events (%.1f%%)\n",
|
||||
proc.ProcessName, proc.EventCount, proc.Percentage)
|
||||
}
|
||||
}
|
||||
|
||||
// Show sample events with PIDs, counts, etc.
|
||||
if len(testResult.Events) > 0 {
|
||||
fmt.Printf(" • Sample events:\n")
|
||||
for j, event := range testResult.Events {
|
||||
if j >= 5 { // Show first 5 events
|
||||
break
|
||||
}
|
||||
fmt.Printf(" [%d] PID:%d TID:%d Process:%s Message:%s\n",
|
||||
j+1, event.PID, event.TID, event.ProcessName, event.Message)
|
||||
}
|
||||
if len(testResult.Events) > 5 {
|
||||
fmt.Printf(" ... and %d more events\n", len(testResult.Events)-5)
|
||||
}
|
||||
}
|
||||
|
||||
if testResult.EventCount > 0 {
|
||||
fmt.Printf(" ✅ Success: Captured %d real syscall events!\n", testResult.EventCount)
|
||||
} else {
|
||||
fmt.Printf(" ⚠️ No events captured (may be normal for this syscall)\n")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n🎉 BCC Trace Manager Root Test Complete!")
|
||||
fmt.Println("✅ Real eBPF tracing is working and ready for production use!")
|
||||
}
|
||||
|
||||
// TestAgentEBPFIntegration tests the agent's integration with BCC-style eBPF tracing
|
||||
// This demonstrates the complete flow from agent to eBPF results
|
||||
func TestAgentEBPFIntegration(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("⚠️ Skipping agent integration test - requires root access")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Agent eBPF Integration Test ===")
|
||||
fmt.Println("This test demonstrates the complete agent flow with BCC-style tracing")
|
||||
|
||||
// Create eBPF manager directly for testing
|
||||
manager := NewBCCTraceManager()
|
||||
|
||||
// Test multiple syscalls that would be sent by remote API
|
||||
testEBPFRequests := []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Target string `json:"target"`
|
||||
Duration int `json:"duration"`
|
||||
Description string `json:"description"`
|
||||
Filters map[string]string `json:"filters"`
|
||||
}{
|
||||
{
|
||||
Name: "file_operations",
|
||||
Type: "syscall",
|
||||
Target: "sys_openat", // Will be converted to __x64_sys_openat
|
||||
Duration: 3,
|
||||
Description: "trace file open operations",
|
||||
Filters: map[string]string{},
|
||||
},
|
||||
{
|
||||
Name: "network_operations",
|
||||
Type: "syscall",
|
||||
Target: "__x64_sys_connect",
|
||||
Duration: 2,
|
||||
Description: "trace network connections",
|
||||
Filters: map[string]string{},
|
||||
},
|
||||
{
|
||||
Name: "io_operations",
|
||||
Type: "syscall",
|
||||
Target: "sys_write",
|
||||
Duration: 2,
|
||||
Description: "trace write operations",
|
||||
Filters: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("🚀 Testing eBPF manager with %d eBPF programs...\n\n", len(testEBPFRequests))
|
||||
|
||||
// Convert to trace specs and execute using manager directly
|
||||
var traceSpecs []TraceSpec
|
||||
for _, req := range testEBPFRequests {
|
||||
spec := TraceSpec{
|
||||
ProbeType: "p", // kprobe
|
||||
Target: "__x64_" + req.Target,
|
||||
Format: req.Description,
|
||||
Duration: req.Duration,
|
||||
}
|
||||
traceSpecs = append(traceSpecs, spec)
|
||||
}
|
||||
|
||||
// Execute traces sequentially for testing
|
||||
var results []map[string]interface{}
|
||||
for i, spec := range traceSpecs {
|
||||
fmt.Printf("Starting trace %d: %s\n", i+1, spec.Target)
|
||||
|
||||
traceID, err := manager.StartTrace(spec)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start trace: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Wait for trace duration
|
||||
time.Sleep(time.Duration(spec.Duration) * time.Second)
|
||||
|
||||
traceResult, err := manager.GetTraceResult(traceID)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get results: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"name": testEBPFRequests[i].Name,
|
||||
"target": spec.Target,
|
||||
"success": true,
|
||||
"event_count": traceResult.EventCount,
|
||||
"summary": traceResult.Summary,
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
fmt.Printf("📊 Agent eBPF Execution Results:\n")
|
||||
fmt.Printf("=" + strings.Repeat("=", 50) + "\n\n")
|
||||
|
||||
for i, result := range results {
|
||||
fmt.Printf("🔍 Program %d: %s\n", i+1, result["name"])
|
||||
fmt.Printf(" Target: %s\n", result["target"])
|
||||
fmt.Printf(" Type: %s\n", result["type"])
|
||||
fmt.Printf(" Status: %s\n", result["status"])
|
||||
fmt.Printf(" Success: %v\n", result["success"])
|
||||
|
||||
if result["success"].(bool) {
|
||||
if eventCount, ok := result["event_count"].(int); ok {
|
||||
fmt.Printf(" Events captured: %d\n", eventCount)
|
||||
}
|
||||
if dataPoints, ok := result["data_points"].(int); ok {
|
||||
fmt.Printf(" Data points: %d\n", dataPoints)
|
||||
}
|
||||
if summary, ok := result["summary"].(string); ok {
|
||||
fmt.Printf(" Summary: %s\n", summary)
|
||||
}
|
||||
|
||||
// Show events if available
|
||||
if events, ok := result["events"].([]TraceEvent); ok && len(events) > 0 {
|
||||
fmt.Printf(" Sample events:\n")
|
||||
for j, event := range events {
|
||||
if j >= 3 { // Show first 3
|
||||
break
|
||||
}
|
||||
fmt.Printf(" [%d] PID:%d Process:%s Message:%s\n",
|
||||
j+1, event.PID, event.ProcessName, event.Message)
|
||||
}
|
||||
if len(events) > 3 {
|
||||
fmt.Printf(" ... and %d more events\n", len(events)-3)
|
||||
}
|
||||
}
|
||||
|
||||
// Show statistics if available
|
||||
if stats, ok := result["statistics"].(TraceStats); ok {
|
||||
fmt.Printf(" Statistics:\n")
|
||||
fmt.Printf(" - Events/sec: %.2f\n", stats.EventsPerSecond)
|
||||
fmt.Printf(" - Total processes: %d\n", len(stats.EventsByProcess))
|
||||
if len(stats.TopProcesses) > 0 {
|
||||
fmt.Printf(" - Top process: %s (%d events)\n",
|
||||
stats.TopProcesses[0].ProcessName, stats.TopProcesses[0].EventCount)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if errMsg, ok := result["error"].(string); ok {
|
||||
fmt.Printf(" Error: %s\n", errMsg)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Validate expected agent response format
|
||||
t.Run("ValidateAgentResponseFormat", func(t *testing.T) {
|
||||
for i, result := range results {
|
||||
// Check required fields
|
||||
requiredFields := []string{"name", "type", "target", "duration", "description", "status", "success"}
|
||||
for _, field := range requiredFields {
|
||||
if _, exists := result[field]; !exists {
|
||||
t.Errorf("Result %d missing required field: %s", i, field)
|
||||
}
|
||||
}
|
||||
|
||||
// If successful, check for data fields
|
||||
if success, ok := result["success"].(bool); ok && success {
|
||||
// Should have either event_count or data_points
|
||||
hasEventCount := false
|
||||
hasDataPoints := false
|
||||
|
||||
if _, ok := result["event_count"]; ok {
|
||||
hasEventCount = true
|
||||
}
|
||||
if _, ok := result["data_points"]; ok {
|
||||
hasDataPoints = true
|
||||
}
|
||||
|
||||
if !hasEventCount && !hasDataPoints {
|
||||
t.Errorf("Successful result %d should have event_count or data_points", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Println("✅ Agent eBPF Integration Test Complete!")
|
||||
fmt.Println("📈 The agent correctly processes eBPF requests and returns detailed syscall data!")
|
||||
}
|
||||
Reference in New Issue
Block a user