Files
log/example/stress/main.go

211 lines
5.5 KiB
Go

// FILE: example/stress/main.go
package main
import (
"fmt"
"math/rand"
"os"
"os/signal"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/lixenwraith/config"
"github.com/lixenwraith/log"
)
const (
totalBursts = 100
logsPerBurst = 500
maxMessageSize = 10000
numWorkers = 500
)
const configFile = "stress_config.toml"
const configBasePath = "logstress" // Base path for log settings in config
// Example TOML content for stress test
var tomlContent = `
# Example stress_config.toml
[logstress]
level = -4 # Debug
name = "stress_test"
directory = "./logs" # Log package will create this
format = "txt"
extension = "log"
show_timestamp = true
show_level = true
buffer_size = 500
max_size_mb = 1 # Force frequent rotation (1MB)
max_total_size_mb = 20 # Limit total size to force cleanup (20MB)
min_disk_free_mb = 50
flush_interval_ms = 50 # ms
trace_depth = 0
retention_period_hrs = 0.0028 # ~10 seconds
retention_check_mins = 0.084 # ~5 seconds
`
var levels = []int64{
log.LevelDebug,
log.LevelInfo,
log.LevelWarn,
log.LevelError,
}
var logger *log.Logger
func generateRandomMessage(size int) string {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "
var sb strings.Builder
sb.Grow(size)
for i := 0; i < size; i++ {
sb.WriteByte(chars[rand.Intn(len(chars))])
}
return sb.String()
}
// logBurst simulates a burst of logging activity
func logBurst(burstID int) {
for i := 0; i < logsPerBurst; i++ {
level := levels[rand.Intn(len(levels))]
msgSize := rand.Intn(maxMessageSize) + 10
msg := generateRandomMessage(msgSize)
args := []any{
msg,
"wkr", burstID % numWorkers,
"bst", burstID,
"seq", i,
"rnd", rand.Int63(),
}
switch level {
case log.LevelDebug:
logger.Debug(args...)
case log.LevelInfo:
logger.Info(args...)
case log.LevelWarn:
logger.Warn(args...)
case log.LevelError:
logger.Error(args...)
}
}
}
// worker goroutine function
func worker(burstChan chan int, wg *sync.WaitGroup, completedBursts *atomic.Int64) {
defer wg.Done()
for burstID := range burstChan {
logBurst(burstID)
completed := completedBursts.Add(1)
if completed%10 == 0 || completed == totalBursts {
fmt.Printf("\rProgress: %d/%d bursts completed", completed, totalBursts)
}
}
}
func main() {
rand.Seed(time.Now().UnixNano()) // Replace rand.New with rand.Seed for compatibility
fmt.Println("--- Logger Stress Test ---")
// --- Setup Config ---
err := os.WriteFile(configFile, []byte(tomlContent), 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write dummy config: %v\n", err)
os.Exit(1)
}
fmt.Printf("Created dummy config file: %s\n", configFile)
logsDir := "./logs" // Match config
_ = os.RemoveAll(logsDir) // Clean previous run's LOGS directory before starting
// defer os.Remove(configFile) // Remove to keep the saved config file
// defer os.RemoveAll(logsDir) // Remove to keep the log directory
cfg := config.New()
err = cfg.Load(configFile, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load config: %v.\n", err)
os.Exit(1)
}
// --- Initialize Logger ---
logger = log.NewLogger()
err = logger.Init(cfg, configBasePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
os.Exit(1)
}
fmt.Printf("Logger initialized. Logs will be written to: %s\n", logsDir)
// --- SAVE CONFIGURATION ---
err = cfg.Save(configFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to save configuration to '%s': %v\n", configFile, err)
} else {
fmt.Printf("Configuration saved to: %s\n", configFile)
}
// --- End Save Configuration ---
fmt.Printf("Starting stress test: %d workers, %d bursts, %d logs/burst.\n",
numWorkers, totalBursts, logsPerBurst)
fmt.Println("Watch for 'Logs were dropped' or 'disk full' messages.")
fmt.Println("Check log directory size and file rotation.")
fmt.Println("Press Ctrl+C to stop early.")
// --- Setup Workers and Signal Handling ---
burstChan := make(chan int, numWorkers)
var wg sync.WaitGroup
completedBursts := atomic.Int64{}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
stopChan := make(chan struct{})
go func() {
<-sigChan
fmt.Println("\n[Signal Received] Stopping burst generation...")
close(stopChan)
}()
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(burstChan, &wg, &completedBursts)
}
// --- Run Test ---
startTime := time.Now()
for i := 1; i <= totalBursts; i++ {
select {
case burstChan <- i:
case <-stopChan:
fmt.Println("[Signal Received] Halting burst submission.")
goto endLoop
}
}
endLoop:
close(burstChan)
fmt.Println("\nWaiting for workers to finish...")
wg.Wait()
duration := time.Since(startTime)
finalCompleted := completedBursts.Load()
fmt.Printf("\n--- Test Finished ---")
fmt.Printf("\nCompleted %d/%d bursts in %v\n", finalCompleted, totalBursts, duration.Round(time.Millisecond))
if finalCompleted > 0 && duration.Seconds() > 0 {
logsPerSec := float64(finalCompleted*logsPerBurst) / duration.Seconds()
fmt.Printf("Approximate Logs/sec: %.2f\n", logsPerSec)
}
// --- Shutdown Logger ---
fmt.Println("Shutting down logger (allowing up to 10s)...")
shutdownTimeout := 10 * time.Second
err = logger.Shutdown(shutdownTimeout)
if err != nil {
fmt.Fprintf(os.Stderr, "Logger shutdown error: %v\n", err)
} else {
fmt.Println("Logger shutdown complete.")
}
fmt.Printf("Check log files in '%s' and the saved config '%s'.\n", logsDir, configFile)
fmt.Println("Check stderr output above for potential errors during cleanup.")
}