173 lines
4.2 KiB
Go
173 lines
4.2 KiB
Go
//go:build linux
|
|
|
|
package collector
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestGetConnections(t *testing.T) {
|
|
// integration test to verify /proc parsing works
|
|
conns, err := GetConnections()
|
|
if err != nil {
|
|
t.Fatalf("GetConnections() returned an error: %v", err)
|
|
}
|
|
|
|
// connections are dynamic, so just verify function succeeded
|
|
t.Logf("Successfully got %d connections", len(conns))
|
|
}
|
|
|
|
func TestGetConnectionsPerformance(t *testing.T) {
|
|
// measures performance to catch regressions
|
|
// run with: go test -v -run TestGetConnectionsPerformance
|
|
|
|
const maxDuration = 500 * time.Millisecond
|
|
const iterations = 5
|
|
|
|
// warm up caches first
|
|
_, err := GetConnections()
|
|
if err != nil {
|
|
t.Fatalf("warmup failed: %v", err)
|
|
}
|
|
|
|
var total time.Duration
|
|
var maxSeen time.Duration
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
start := time.Now()
|
|
conns, err := GetConnections()
|
|
elapsed := time.Since(start)
|
|
|
|
if err != nil {
|
|
t.Fatalf("iteration %d failed: %v", i, err)
|
|
}
|
|
|
|
total += elapsed
|
|
if elapsed > maxSeen {
|
|
maxSeen = elapsed
|
|
}
|
|
|
|
t.Logf("iteration %d: %v (%d connections)", i+1, elapsed, len(conns))
|
|
}
|
|
|
|
avg := total / time.Duration(iterations)
|
|
t.Logf("average: %v, max: %v", avg, maxSeen)
|
|
|
|
if maxSeen > maxDuration {
|
|
t.Errorf("slowest iteration took %v, expected < %v", maxSeen, maxDuration)
|
|
}
|
|
}
|
|
|
|
func TestGetConnectionsColdCache(t *testing.T) {
|
|
// tests performance with cold user cache
|
|
// this simulates first run or after cache invalidation
|
|
|
|
const maxDuration = 2 * time.Second
|
|
|
|
clearUserCache()
|
|
|
|
start := time.Now()
|
|
conns, err := GetConnections()
|
|
elapsed := time.Since(start)
|
|
|
|
if err != nil {
|
|
t.Fatalf("GetConnections() failed: %v", err)
|
|
}
|
|
|
|
t.Logf("cold cache: %v (%d connections, %d cached users after)",
|
|
elapsed, len(conns), userCacheSize())
|
|
|
|
if elapsed > maxDuration {
|
|
t.Errorf("cold cache took %v, expected < %v", elapsed, maxDuration)
|
|
}
|
|
}
|
|
|
|
func BenchmarkGetConnections(b *testing.B) {
|
|
// warm cache benchmark - measures typical runtime
|
|
// run with: go test -bench=BenchmarkGetConnections -benchtime=5s
|
|
|
|
// warm up
|
|
_, _ = GetConnections()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = GetConnections()
|
|
}
|
|
}
|
|
|
|
func BenchmarkGetConnectionsColdCache(b *testing.B) {
|
|
// cold cache benchmark - measures worst-case with cache cleared each iteration
|
|
// run with: go test -bench=BenchmarkGetConnectionsColdCache -benchtime=10s
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
clearUserCache()
|
|
_, _ = GetConnections()
|
|
}
|
|
}
|
|
|
|
func BenchmarkBuildInodeMap(b *testing.B) {
|
|
// benchmarks just the inode map building (most expensive part)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = buildInodeToProcessMap()
|
|
}
|
|
}
|
|
|
|
func TestConnectionHasCmdlineAndCwd(t *testing.T) {
|
|
conns, err := GetConnections()
|
|
if err != nil {
|
|
t.Fatalf("GetConnections() returned an error: %v", err)
|
|
}
|
|
|
|
if len(conns) == 0 {
|
|
t.Skip("no connections to test")
|
|
}
|
|
|
|
// find a connection with a PID (owned by some process)
|
|
var connWithProcess *Connection
|
|
for i := range conns {
|
|
if conns[i].PID > 0 {
|
|
connWithProcess = &conns[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if connWithProcess == nil {
|
|
t.Skip("no connections with associated process found")
|
|
}
|
|
|
|
t.Logf("testing connection: pid=%d process=%s", connWithProcess.PID, connWithProcess.Process)
|
|
|
|
// cmdline and cwd should be populated for connections with PIDs
|
|
// note: they might be empty if we don't have permission to read them
|
|
if connWithProcess.Cmdline != "" {
|
|
t.Logf("cmdline: %s", connWithProcess.Cmdline)
|
|
} else {
|
|
t.Logf("cmdline is empty (might be permission issue)")
|
|
}
|
|
|
|
if connWithProcess.Cwd != "" {
|
|
t.Logf("cwd: %s", connWithProcess.Cwd)
|
|
} else {
|
|
t.Logf("cwd is empty (might be permission issue)")
|
|
}
|
|
}
|
|
|
|
func TestGetProcessInfoPopulatesCmdlineAndCwd(t *testing.T) {
|
|
// test that getProcessInfo correctly populates cmdline and cwd for our own process
|
|
info, err := getProcessInfo(1) // init process (usually has cwd of /)
|
|
if err != nil {
|
|
t.Logf("could not get process info for pid 1: %v", err)
|
|
t.Skip("skipping - may not have permission")
|
|
}
|
|
|
|
t.Logf("pid 1 info: command=%s cmdline=%s cwd=%s", info.command, info.cmdline, info.cwd)
|
|
|
|
// at minimum, we should have a command name
|
|
if info.command == "" && info.cmdline == "" {
|
|
t.Error("expected either command or cmdline to be populated")
|
|
}
|
|
} |