mirror of
https://github.com/ivuorinen/f2b.git
synced 2026-03-07 09:58:08 +00:00
* fix: repair Renovate config and convert Makefile to go run (#117) - Remove non-existent `github>renovatebot/presets:golang` preset that broke Renovate PR creation - Replace deprecated `fileMatch` with `managerFilePatterns` in customManagers - Rewrite regex to match new Makefile pattern (renovate comment above version variable assignment) - Fix `matchFileNames` glob pattern (`*.mk` -> `**/*.mk`) - Convert all tool invocations from `go install` + global binary to `go run tool@version` for reproducible builds - Convert npm global tools to `npx --yes` invocations - Remove `dev-deps` and `check-deps` targets (tools auto-download) - Add mdformat pre-commit hook with GFM support and config - Add `fmt-md` Makefile target for manual markdown formatting - Update local golangci-lint pre-commit hook to use `go run` - Apply golangci-lint v2.10.1 auto-fixes (fmt.Fprintf optimization) - Add nolint:gosec annotations for legitimate exec.Command usage - Exclude .serena/ from mdformat and megalinter - Add markdown indent_size=unset in .editorconfig for CommonMark compat * chore(deps): update GitHub Actions to latest versions - anthropics/claude-code-action: v1.0.34 -> v1.0.64 - actions/setup-go: v6.2.0 -> v6.3.0 - actions/upload-artifact: v6.0.0 -> v7.0.0 - goreleaser/goreleaser-action: v6.4.0 -> v7.0.0 - docker/login-action: v3.6.0 -> v3.7.0 - ivuorinen/actions: v2026.01.21 -> v2026.02.24 * fix: address code review feedback - Fix issue template YAML frontmatter (replace underscore separators with proper --- delimiters); exclude templates from mdformat - Replace string(rune(n)) with strconv.Itoa(n) in test files to produce deterministic numeric directory names instead of Unicode characters - Remove stale `make dev-deps` reference in README, replace with `make dev-setup` - Extract ban/unban format strings into shared.MetricsFmtBanOperations and shared.MetricsFmtUnbanOperations constants - Replace hardcoded coverage percentages in README with evergreen phrasing * fix: address round 2 code review feedback for PR #120 - Fix corrupted path traversal example in docs/security.md - Fix Renovate .mk regex to match nested paths (.*\.mk$) - Update checkmake pre-commit hook to v0.3.2 to match Makefile - Add sync.WaitGroup to unsynchronized goroutines in security tests - Fix fmt-md target to use pre-commit run mdformat - Pin markdownlint-cli2 to v0.21.0 in lint-md target - Standardize //nolint:gosec to // #nosec annotations for gosec CLI * fix(ci): install PyYAML dependency for PR lint workflow The pr-lint workflow uses ivuorinen/actions/pr-lint which internally calls validate-inputs running a Python script that imports yaml. Python was set up but PyYAML was never installed, causing ModuleNotFoundError at runtime. * fix: address round 3 code review feedback for PR #120 - Wrap Windows-style path traversal example in backtick code span so backslashes render literally in docs/security.md - Add Renovate-managed MARKDOWNLINT_CLI2_VERSION variable in Makefile to match the pattern used by all other tool versions
252 lines
6.9 KiB
Go
252 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ivuorinen/f2b/fail2ban"
|
|
)
|
|
|
|
// BenchmarkE2E_MainAPIs benchmarks the main API functions end-to-end
|
|
func BenchmarkE2E_MainAPIs(b *testing.B) {
|
|
// Setup test environment
|
|
tempDir := b.TempDir()
|
|
fail2ban.SetLogDir(tempDir)
|
|
|
|
// Create test log file with realistic content
|
|
testLogFile := filepath.Join(tempDir, "fail2ban.log")
|
|
testContent := []byte(`2024-01-01 12:00:00,123 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.100
|
|
2024-01-01 12:01:00,456 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.101
|
|
2024-01-01 12:02:00,789 fail2ban.actions [1234]: NOTICE [nginx] Ban 192.168.1.102
|
|
2024-01-01 12:03:00,012 fail2ban.actions [1234]: NOTICE [sshd] Unban 192.168.1.100
|
|
`)
|
|
err := os.WriteFile(testLogFile, testContent, 0600)
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test log file: %v", err)
|
|
}
|
|
|
|
// Setup mock client
|
|
client := setupBenchmarkClient(b)
|
|
|
|
b.Run("GetLogLines", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := fail2ban.GetLogLines(context.Background(), "sshd", "192.168.1.100")
|
|
if err != nil {
|
|
b.Fatalf("GetLogLines failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("GetLogLinesWithLimit", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := fail2ban.GetLogLinesWithLimit(context.Background(), "sshd", "192.168.1.100", 100)
|
|
if err != nil {
|
|
b.Fatalf("GetLogLinesWithLimit failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("UltraOptimized", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := fail2ban.GetLogLinesUltraOptimized("sshd", "192.168.1.100", 100)
|
|
if err != nil {
|
|
b.Fatalf("GetLogLinesUltraOptimized failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("Client_GetBanRecords", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := client.GetBanRecords([]string{"sshd"})
|
|
if err != nil {
|
|
b.Fatalf("GetBanRecords failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("Client_GetBanRecordsWithContext", func(b *testing.B) {
|
|
ctx := context.Background()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := client.GetBanRecordsWithContext(ctx, []string{"sshd"})
|
|
if err != nil {
|
|
b.Fatalf("GetBanRecordsWithContext failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// BenchmarkMemoryAllocation_Critical benchmarks memory allocations in critical paths
|
|
func BenchmarkMemoryAllocation_Critical(b *testing.B) {
|
|
tempDir := b.TempDir()
|
|
fail2ban.SetLogDir(tempDir)
|
|
|
|
// Create test log file with proper line structure
|
|
testLogFile := filepath.Join(tempDir, "fail2ban.log")
|
|
testLine := "2024-01-01 12:00:00,123 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.100\n"
|
|
|
|
// Create 1MB of realistic log data (about 10,000 lines)
|
|
var testContent []byte
|
|
for len(testContent) < 1024*1024 {
|
|
testContent = append(testContent, testLine...)
|
|
}
|
|
err := os.WriteFile(testLogFile, testContent, 0600)
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test log file: %v", err)
|
|
}
|
|
|
|
b.Run("LargeLogProcessing", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := fail2ban.GetLogLinesWithLimit(context.Background(), "all", "all", 1000)
|
|
if err != nil {
|
|
b.Fatalf("Large log processing failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("StringPoolingEfficiency", func(b *testing.B) {
|
|
processor := fail2ban.NewOptimizedLogProcessor()
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := processor.GetLogLinesOptimized("sshd", "192.168.1.100", 100)
|
|
if err != nil {
|
|
b.Fatalf("String pooling test failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// BenchmarkConcurrentPerformance benchmarks performance under concurrent load
|
|
func BenchmarkConcurrentPerformance(b *testing.B) {
|
|
tempDir := b.TempDir()
|
|
fail2ban.SetLogDir(tempDir)
|
|
|
|
// Create test log file
|
|
testLogFile := filepath.Join(tempDir, "fail2ban.log")
|
|
testContent := []byte(`2024-01-01 12:00:00,123 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.100
|
|
2024-01-01 12:01:00,456 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.101
|
|
`)
|
|
err := os.WriteFile(testLogFile, testContent, 0600)
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test log file: %v", err)
|
|
}
|
|
|
|
b.Run("ConcurrentLogReading", func(b *testing.B) {
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
_, err := fail2ban.GetLogLinesUltraOptimized("sshd", "all", 50)
|
|
if err != nil {
|
|
b.Fatalf("Concurrent log reading failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("ConcurrentCacheAccess", func(b *testing.B) {
|
|
processor := fail2ban.NewOptimizedLogProcessor()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
// Mix cache operations
|
|
processor.GetCacheStats()
|
|
if pb.Next() {
|
|
_, _ = processor.GetLogLinesOptimized("all", "all", 10)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// BenchmarkGlobalStateAccess benchmarks thread-safe global state access
|
|
func BenchmarkGlobalStateAccess(b *testing.B) {
|
|
originalLogDir := fail2ban.GetLogDir()
|
|
defer fail2ban.SetLogDir(originalLogDir)
|
|
|
|
b.Run("LogDirAccess", func(b *testing.B) {
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
if pb.Next() {
|
|
// 50% reads
|
|
fail2ban.GetLogDir()
|
|
} else {
|
|
// 50% writes
|
|
fail2ban.SetLogDir("/tmp/test-" + strconv.Itoa(b.N))
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// setupBenchmarkClient creates a properly configured client for benchmarking
|
|
func setupBenchmarkClient(b *testing.B) *fail2ban.RealClient {
|
|
b.Helper()
|
|
|
|
// Setup mock environment
|
|
_, cleanup := fail2ban.SetupMockEnvironment(b)
|
|
b.Cleanup(cleanup)
|
|
|
|
// Get the mock runner and add additional responses
|
|
mockRunner := fail2ban.GetRunner().(*fail2ban.MockRunner)
|
|
mockRunner.SetResponse("fail2ban-client -V", []byte("fail2ban-client v1.0.0"))
|
|
mockRunner.SetResponse("fail2ban-client status", []byte("Status: [sshd] Jail list: sshd"))
|
|
mockRunner.SetResponse(
|
|
"sudo fail2ban-client get sshd banip --with-time",
|
|
[]byte(`192.168.1.100 2024-01-01 12:00:00
|
|
192.168.1.101 2024-01-01 12:01:00`),
|
|
)
|
|
|
|
client, err := fail2ban.NewClient("", "")
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test client: %v", err)
|
|
}
|
|
|
|
return client
|
|
}
|
|
|
|
// MemoryProfile runs a memory profiling session
|
|
func MemoryProfile(b *testing.B) {
|
|
b.Helper()
|
|
tempDir := b.TempDir()
|
|
fail2ban.SetLogDir(tempDir)
|
|
|
|
// Create large test log file (10MB)
|
|
testLogFile := filepath.Join(tempDir, "fail2ban.log")
|
|
testLine := "2024-01-01 12:00:00,123 fail2ban.actions [1234]: NOTICE [sshd] Ban 192.168.1.100\n"
|
|
|
|
// Create 10MB of realistic log data (about 100,000 lines)
|
|
var largeContent []byte
|
|
for len(largeContent) < 10*1024*1024 {
|
|
largeContent = append(largeContent, testLine...)
|
|
}
|
|
|
|
err := os.WriteFile(testLogFile, largeContent, 0600)
|
|
if err != nil {
|
|
b.Fatalf("Failed to create large test file: %v", err)
|
|
}
|
|
|
|
b.Run("LargeFileMemoryUsage", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
start := time.Now()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := fail2ban.GetLogLinesUltraOptimized("all", "all", 10000)
|
|
if err != nil {
|
|
b.Fatalf("Large file processing failed: %v", err)
|
|
}
|
|
}
|
|
|
|
duration := time.Since(start)
|
|
b.Logf("Processing 10MB file took %v", duration)
|
|
})
|
|
}
|