realistic_benchmark_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package nginx_log
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "testing"
  8. "time"
  9. "github.com/0xJacky/Nginx-UI/internal/nginx_log/indexer"
  10. "github.com/0xJacky/Nginx-UI/internal/nginx_log/parser"
  11. )
  12. // BenchmarkRealisticProduction benchmarks the complete production pipeline
  13. func BenchmarkRealisticProduction(b *testing.B) {
  14. // Test different scales
  15. scales := []struct {
  16. name string
  17. records int
  18. }{
  19. {"Small_1K", 1000},
  20. {"Medium_5K", 5000},
  21. }
  22. for _, scale := range scales {
  23. b.Run(scale.name, func(b *testing.B) {
  24. benchmarkCompleteProduction(b, scale.records)
  25. })
  26. }
  27. }
  28. func benchmarkCompleteProduction(b *testing.B, recordCount int) {
  29. // Setup once
  30. tempDir, err := os.MkdirTemp("", "benchmark_production_")
  31. if err != nil {
  32. b.Fatalf("Failed to create temp dir: %v", err)
  33. }
  34. defer os.RemoveAll(tempDir)
  35. // Generate test data once
  36. testLogFile := filepath.Join(tempDir, "access.log")
  37. if err := generateBenchmarkLogFile(testLogFile, recordCount); err != nil {
  38. b.Fatalf("Failed to generate test data: %v", err)
  39. }
  40. // Setup production environment
  41. indexDir := filepath.Join(tempDir, "index")
  42. config := indexer.DefaultIndexerConfig()
  43. config.IndexPath = indexDir
  44. config.WorkerCount = 8
  45. config.BatchSize = 500
  46. config.EnableMetrics = false // Disable for cleaner benchmarking
  47. // Create production-like services
  48. geoService := &BenchGeoIPService{}
  49. userAgentParser := parser.NewSimpleUserAgentParser()
  50. optimizedParser := parser.NewOptimizedParser(
  51. &parser.Config{
  52. MaxLineLength: 4 * 1024,
  53. WorkerCount: 4,
  54. BatchSize: 200,
  55. },
  56. userAgentParser,
  57. geoService,
  58. )
  59. b.ResetTimer()
  60. b.ReportAllocs()
  61. // Run the benchmark
  62. for i := 0; i < b.N; i++ {
  63. // Create fresh index for each iteration
  64. iterIndexDir := filepath.Join(tempDir, fmt.Sprintf("index_%d", i))
  65. iterConfig := *config
  66. iterConfig.IndexPath = iterIndexDir
  67. result := runBenchmarkProduction(b, &iterConfig, optimizedParser, testLogFile)
  68. // Custom metrics
  69. throughput := float64(recordCount) / result.Duration.Seconds()
  70. b.ReportMetric(throughput, "records/sec")
  71. b.ReportMetric(float64(result.Processed), "records_processed")
  72. b.ReportMetric(float64(result.Indexed), "records_indexed")
  73. b.ReportMetric(result.SuccessRate*100, "success_rate_%")
  74. }
  75. }
  76. type BenchResult struct {
  77. Duration time.Duration
  78. Processed int
  79. Indexed int
  80. SuccessRate float64
  81. }
  82. func runBenchmarkProduction(b *testing.B, config *indexer.Config, optimizedParser *parser.OptimizedParser, logFile string) *BenchResult {
  83. start := time.Now()
  84. // Create indexer
  85. if err := os.MkdirAll(config.IndexPath, 0755); err != nil {
  86. b.Fatalf("Failed to create index dir: %v", err)
  87. }
  88. shardManager := indexer.NewGroupedShardManager(config)
  89. indexerInstance := indexer.NewParallelIndexer(config, shardManager)
  90. ctx := context.Background()
  91. if err := indexerInstance.Start(ctx); err != nil {
  92. b.Fatalf("Failed to start indexer: %v", err)
  93. }
  94. defer indexerInstance.Stop()
  95. // Parse
  96. file, err := os.Open(logFile)
  97. if err != nil {
  98. b.Fatalf("Failed to open log file: %v", err)
  99. }
  100. defer file.Close()
  101. parseResult, err := optimizedParser.OptimizedParseStream(ctx, file)
  102. if err != nil {
  103. b.Fatalf("Parsing failed: %v", err)
  104. }
  105. // Index (limit to avoid timeout in benchmarking)
  106. maxToIndex := minVal(len(parseResult.Entries), 1000)
  107. indexed := 0
  108. for i, entry := range parseResult.Entries[:maxToIndex] {
  109. doc := &indexer.Document{
  110. ID: fmt.Sprintf("doc_%d", i),
  111. Fields: &indexer.LogDocument{
  112. Timestamp: entry.Timestamp,
  113. IP: entry.IP,
  114. Method: entry.Method,
  115. Path: entry.Path,
  116. PathExact: entry.Path,
  117. Status: entry.Status,
  118. BytesSent: entry.BytesSent,
  119. UserAgent: entry.UserAgent,
  120. FilePath: logFile,
  121. MainLogPath: logFile,
  122. Raw: entry.Raw,
  123. },
  124. }
  125. if err := indexerInstance.IndexDocument(ctx, doc); err == nil {
  126. indexed++
  127. }
  128. }
  129. // Flush
  130. indexerInstance.FlushAll()
  131. duration := time.Since(start)
  132. return &BenchResult{
  133. Duration: duration,
  134. Processed: parseResult.Processed,
  135. Indexed: indexed,
  136. SuccessRate: float64(indexed) / float64(maxToIndex),
  137. }
  138. }
  139. func generateBenchmarkLogFile(filename string, recordCount int) error {
  140. file, err := os.Create(filename)
  141. if err != nil {
  142. return err
  143. }
  144. defer file.Close()
  145. baseTime := time.Now().Unix() - 3600
  146. for i := 0; i < recordCount; i++ {
  147. timestamp := baseTime + int64(i%3600)
  148. ip := fmt.Sprintf("10.0.%d.%d", (i/254)%256, i%254+1)
  149. path := []string{"/", "/api", "/health", "/metrics"}[i%4]
  150. status := []int{200, 200, 200, 404}[i%4]
  151. size := 1000 + i%2000
  152. logLine := fmt.Sprintf(
  153. `%s - - [%s] "GET %s HTTP/1.1" %d %d "-" "TestAgent/1.0" 0.%03d`,
  154. ip,
  155. time.Unix(timestamp, 0).Format("02/Jan/2006:15:04:05 -0700"),
  156. path,
  157. status,
  158. size,
  159. i%1000,
  160. )
  161. if _, err := fmt.Fprintln(file, logLine); err != nil {
  162. return err
  163. }
  164. }
  165. return nil
  166. }
  167. type BenchGeoIPService struct{}
  168. func (s *BenchGeoIPService) Search(ip string) (*parser.GeoLocation, error) {
  169. return &parser.GeoLocation{
  170. CountryCode: "US",
  171. RegionCode: "CA",
  172. Province: "California",
  173. City: "San Francisco",
  174. }, nil
  175. }
  176. func minVal(a, b int) int {
  177. if a < b {
  178. return a
  179. }
  180. return b
  181. }