1
0

optimization_integration_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. package nginx_log
  2. import (
  3. "context"
  4. "strings"
  5. "testing"
  6. "time"
  7. "github.com/0xJacky/Nginx-UI/internal/nginx_log/analytics"
  8. "github.com/0xJacky/Nginx-UI/internal/nginx_log/parser"
  9. "github.com/0xJacky/Nginx-UI/internal/nginx_log/utils"
  10. )
  11. // TestOptimizationSystemIntegration tests all optimization components working together
  12. func TestOptimizationSystemIntegration(t *testing.T) {
  13. if testing.Short() {
  14. t.Skip("Skipping optimization integration test in short mode")
  15. }
  16. // Test data - realistic nginx log entries
  17. testLogData := generateIntegrationTestData(1000)
  18. ctx := context.Background()
  19. t.Run("CompleteOptimizationPipeline", func(t *testing.T) {
  20. // Test 1: Optimized Parser with all enhancements
  21. config := parser.DefaultParserConfig()
  22. config.MaxLineLength = 16 * 1024
  23. config.BatchSize = 500
  24. optimizedParser := parser.NewParser(
  25. config,
  26. parser.NewCachedUserAgentParser(parser.NewSimpleUserAgentParser(), 1000),
  27. &mockGeoIPService{},
  28. )
  29. // Performance measurement
  30. start := time.Now()
  31. // Test ParseStream
  32. parseResult, err := optimizedParser.ParseStream(ctx, strings.NewReader(testLogData))
  33. if err != nil {
  34. t.Fatalf("ParseStream failed: %v", err)
  35. }
  36. optimizedParseTime := time.Since(start)
  37. optimizedRate := float64(parseResult.Processed) / optimizedParseTime.Seconds()
  38. // Test 2: SIMD Parser performance
  39. start = time.Now()
  40. simdParser := parser.NewLogLineParser()
  41. lines := strings.Split(testLogData, "\n")
  42. logBytes := make([][]byte, 0, len(lines))
  43. for _, line := range lines {
  44. if strings.TrimSpace(line) != "" {
  45. logBytes = append(logBytes, []byte(line))
  46. }
  47. }
  48. simdEntries := simdParser.ParseLines(logBytes)
  49. simdParseTime := time.Since(start)
  50. simdRate := float64(len(simdEntries)) / simdParseTime.Seconds()
  51. // Test 3: Enhanced Memory Pools under load
  52. start = time.Now()
  53. poolOperations := 10000
  54. for i := 0; i < poolOperations; i++ {
  55. // String builder pool
  56. sb := utils.LogStringBuilderPool.Get()
  57. sb.WriteString("integration test data")
  58. utils.LogStringBuilderPool.Put(sb)
  59. // Byte slice pool
  60. slice := utils.GlobalByteSlicePool.Get(1024)
  61. utils.GlobalByteSlicePool.Put(slice)
  62. }
  63. poolTime := time.Since(start)
  64. poolRate := float64(poolOperations*2) / poolTime.Seconds() // 2 operations per iteration
  65. // Test 4: Time-series analytics with optimizations
  66. start = time.Now()
  67. // Create realistic time-series data
  68. timeSeriesData := make([]analytics.TimeValue, len(simdEntries))
  69. baseTime := time.Now().Unix()
  70. for i := range simdEntries {
  71. timeSeriesData[i] = analytics.TimeValue{
  72. Timestamp: baseTime + int64(i*60), // 1 minute intervals
  73. Value: 1 + (i % 10), // Varied values
  74. }
  75. }
  76. // Advanced analytics
  77. advancedProcessor := analytics.NewAnomalyDetector()
  78. anomalies := advancedProcessor.DetectAnomalies(timeSeriesData)
  79. trend := advancedProcessor.CalculateTrend(timeSeriesData)
  80. analyticsTime := time.Since(start)
  81. // Test 5: Regex caching performance
  82. start = time.Now()
  83. cache := parser.GetGlobalRegexCache()
  84. regexOperations := 10000
  85. for i := 0; i < regexOperations; i++ {
  86. // Should hit cache frequently
  87. _, _ = cache.GetCommonRegex("ipv4")
  88. _, _ = cache.GetCommonRegex("timestamp")
  89. _, _ = cache.GetCommonRegex("status")
  90. }
  91. regexTime := time.Since(start)
  92. regexRate := float64(regexOperations*3) / regexTime.Seconds()
  93. // Performance assertions and reporting
  94. t.Logf("=== OPTIMIZATION INTEGRATION RESULTS ===")
  95. t.Logf("Optimized Parser: %d lines in %v (%.2f lines/sec)",
  96. parseResult.Processed, optimizedParseTime, optimizedRate)
  97. t.Logf("SIMD Parser: %d lines in %v (%.2f lines/sec)",
  98. len(simdEntries), simdParseTime, simdRate)
  99. t.Logf("Memory Pools: %d ops in %v (%.2f ops/sec)",
  100. poolOperations*2, poolTime, poolRate)
  101. t.Logf("Analytics: Processed in %v, %d anomalies, trend: %s",
  102. analyticsTime, len(anomalies), trend.Direction)
  103. t.Logf("Regex Cache: %d ops in %v (%.2f ops/sec)",
  104. regexOperations*3, regexTime, regexRate)
  105. // Performance requirements validation
  106. minOptimizedRate := 500.0 // lines/sec
  107. minSIMDRate := 10000.0 // lines/sec
  108. minPoolRate := 100000.0 // ops/sec
  109. minRegexRate := 1000000.0 // ops/sec
  110. if optimizedRate < minOptimizedRate {
  111. t.Errorf("Optimized parser rate %.2f < expected %.2f lines/sec",
  112. optimizedRate, minOptimizedRate)
  113. }
  114. if simdRate < minSIMDRate {
  115. t.Errorf("SIMD parser rate %.2f < expected %.2f lines/sec",
  116. simdRate, minSIMDRate)
  117. }
  118. if poolRate < minPoolRate {
  119. t.Errorf("Memory pool rate %.2f < expected %.2f ops/sec",
  120. poolRate, minPoolRate)
  121. }
  122. if regexRate < minRegexRate {
  123. t.Errorf("Regex cache rate %.2f < expected %.2f ops/sec",
  124. regexRate, minRegexRate)
  125. }
  126. // Data integrity validation - both parsers should process same number of lines
  127. expectedLines := len(strings.Split(strings.TrimSpace(testLogData), "\n"))
  128. if parseResult.Processed != expectedLines {
  129. t.Errorf("Optimized parser processed %d lines, expected %d",
  130. parseResult.Processed, expectedLines)
  131. }
  132. if len(simdEntries) != expectedLines {
  133. t.Errorf("SIMD parser processed %d lines, expected %d",
  134. len(simdEntries), expectedLines)
  135. }
  136. // Test parsing consistency with a known log line
  137. testLine := `192.168.1.100 - - [06/Sep/2025:10:00:00 +0000] "GET /test HTTP/1.1" 200 1024 "https://example.com" "Mozilla/5.0"`
  138. // Parse with optimized parser
  139. optimizedTest, err := optimizedParser.ParseStream(ctx, strings.NewReader(testLine))
  140. if err != nil || len(optimizedTest.Entries) == 0 {
  141. t.Errorf("Optimized parser failed on test line: %v", err)
  142. } else {
  143. // Parse with SIMD parser
  144. simdTestEntry := simdParser.ParseLine([]byte(testLine))
  145. if simdTestEntry == nil {
  146. t.Error("SIMD parser failed on test line")
  147. } else {
  148. // Compare the results
  149. optimizedTestEntry := optimizedTest.Entries[0]
  150. if optimizedTestEntry.IP != simdTestEntry.IP {
  151. t.Errorf("Test line IP mismatch: optimized=%s, simd=%s",
  152. optimizedTestEntry.IP, simdTestEntry.IP)
  153. }
  154. if optimizedTestEntry.Status != simdTestEntry.Status {
  155. t.Errorf("Test line status mismatch: optimized=%d, simd=%d",
  156. optimizedTestEntry.Status, simdTestEntry.Status)
  157. }
  158. }
  159. }
  160. })
  161. t.Run("ResourceEfficiencyValidation", func(t *testing.T) {
  162. // Test memory pool efficiency
  163. poolManager := utils.GetGlobalPoolManager()
  164. _ = poolManager.GetAllStats() // Get initial stats for reference
  165. // Perform intensive operations
  166. iterations := 5000
  167. for i := 0; i < iterations; i++ {
  168. // String builder operations
  169. sb := utils.LogStringBuilderPool.Get()
  170. sb.WriteString("efficiency test data for memory pools")
  171. utils.LogStringBuilderPool.Put(sb)
  172. // Byte slice operations
  173. slice := utils.GlobalByteSlicePool.Get(2048)
  174. copy(slice[:10], []byte("test data"))
  175. utils.GlobalByteSlicePool.Put(slice)
  176. // Worker operations
  177. worker := utils.NewPooledWorker()
  178. testData := []byte("worker efficiency test")
  179. worker.ProcessWithPools(testData, func(data []byte, sb *strings.Builder) error {
  180. sb.WriteString(string(data))
  181. return nil
  182. })
  183. worker.Cleanup()
  184. }
  185. finalStats := poolManager.GetAllStats()
  186. // Verify pool efficiency
  187. t.Logf("=== POOL EFFICIENCY RESULTS ===")
  188. for name, stats := range finalStats {
  189. if statsObj, ok := stats.(map[string]interface{}); ok {
  190. t.Logf("Pool %s: %+v", name, statsObj)
  191. } else {
  192. t.Logf("Pool %s: %+v", name, stats)
  193. }
  194. }
  195. // Test regex cache efficiency
  196. cache := parser.GetGlobalRegexCache()
  197. initialCacheStats := cache.GetStats()
  198. // Perform regex operations
  199. for i := 0; i < 1000; i++ {
  200. _, _ = cache.GetCommonRegex("ipv4")
  201. _, _ = cache.GetCommonRegex("timestamp")
  202. _, _ = cache.GetCommonRegex("combined_format")
  203. }
  204. finalCacheStats := cache.GetStats()
  205. hitRateImprovement := finalCacheStats.HitRate - initialCacheStats.HitRate
  206. t.Logf("=== CACHE EFFICIENCY RESULTS ===")
  207. t.Logf("Initial: Hits=%d, Misses=%d, HitRate=%.2f%%",
  208. initialCacheStats.Hits, initialCacheStats.Misses, initialCacheStats.HitRate*100)
  209. t.Logf("Final: Hits=%d, Misses=%d, HitRate=%.2f%%",
  210. finalCacheStats.Hits, finalCacheStats.Misses, finalCacheStats.HitRate*100)
  211. t.Logf("Hit rate improvement: %.2f%%", hitRateImprovement*100)
  212. // Cache hit rate should be very high
  213. if finalCacheStats.HitRate < 0.9 {
  214. t.Errorf("Cache hit rate %.2f%% is too low, expected > 90%%",
  215. finalCacheStats.HitRate*100)
  216. }
  217. })
  218. t.Run("StressTestOptimizations", func(t *testing.T) {
  219. // Stress test with concurrent operations
  220. concurrency := 10
  221. operationsPerGoroutine := 1000
  222. // Channel to collect results
  223. results := make(chan time.Duration, concurrency*3)
  224. // Start concurrent goroutines
  225. for i := 0; i < concurrency; i++ {
  226. go func(id int) {
  227. // SIMD parsing stress test
  228. start := time.Now()
  229. simdParser := parser.NewLogLineParser()
  230. testLine := `192.168.1.100 - - [06/Sep/2025:10:00:00 +0000] "GET /stress HTTP/1.1" 200 1024 "https://test.com" "StressTest/1.0"`
  231. for j := 0; j < operationsPerGoroutine; j++ {
  232. _ = simdParser.ParseLine([]byte(testLine))
  233. }
  234. results <- time.Since(start)
  235. // Memory pool stress test
  236. start = time.Now()
  237. for j := 0; j < operationsPerGoroutine; j++ {
  238. sb := utils.LogStringBuilderPool.Get()
  239. sb.WriteString("concurrent stress test")
  240. utils.LogStringBuilderPool.Put(sb)
  241. }
  242. results <- time.Since(start)
  243. // Regex cache stress test
  244. start = time.Now()
  245. cache := parser.GetGlobalRegexCache()
  246. for j := 0; j < operationsPerGoroutine; j++ {
  247. _, _ = cache.GetCommonRegex("ipv4")
  248. }
  249. results <- time.Since(start)
  250. }(i)
  251. }
  252. // Collect results
  253. totalDuration := time.Duration(0)
  254. resultCount := 0
  255. timeout := time.After(30 * time.Second)
  256. for resultCount < concurrency*3 {
  257. select {
  258. case duration := <-results:
  259. totalDuration += duration
  260. resultCount++
  261. case <-timeout:
  262. t.Fatal("Stress test timed out")
  263. }
  264. }
  265. averageDuration := totalDuration / time.Duration(resultCount)
  266. totalOperations := concurrency * operationsPerGoroutine * 3
  267. overallRate := float64(totalOperations) / totalDuration.Seconds()
  268. t.Logf("=== STRESS TEST RESULTS ===")
  269. t.Logf("Total operations: %d", totalOperations)
  270. t.Logf("Total time: %v", totalDuration)
  271. t.Logf("Average time per goroutine: %v", averageDuration)
  272. t.Logf("Overall rate: %.2f ops/sec", overallRate)
  273. // Performance assertion
  274. minStressRate := 10000.0 // ops/sec under stress
  275. if overallRate < minStressRate {
  276. t.Errorf("Stress test rate %.2f < expected %.2f ops/sec",
  277. overallRate, minStressRate)
  278. }
  279. })
  280. }
  281. // TestOptimizationCorrectness validates that all optimizations produce correct results
  282. func TestOptimizationCorrectness(t *testing.T) {
  283. testLogLine := `127.0.0.1 - - [06/Sep/2025:10:00:00 +0000] "GET /test.html HTTP/1.1" 200 2048 "https://example.com" "Mozilla/5.0"`
  284. ctx := context.Background()
  285. // Test all parsing methods produce identical results
  286. t.Run("ParsingMethodConsistency", func(t *testing.T) {
  287. // Standard parser
  288. config := parser.DefaultParserConfig()
  289. config.MaxLineLength = 16 * 1024
  290. standardParser := parser.NewParser(
  291. config,
  292. parser.NewSimpleUserAgentParser(),
  293. &mockGeoIPService{},
  294. )
  295. standardResult, err := standardParser.ParseStream(ctx, strings.NewReader(testLogLine))
  296. if err != nil || len(standardResult.Entries) == 0 {
  297. t.Fatalf("Standard parser failed: %v", err)
  298. }
  299. // Optimized parser
  300. optimizedResult, err := standardParser.ParseStream(ctx, strings.NewReader(testLogLine))
  301. if err != nil || len(optimizedResult.Entries) == 0 {
  302. t.Fatalf("Optimized parser failed: %v", err)
  303. }
  304. // SIMD parser
  305. simdParser := parser.NewLogLineParser()
  306. simdEntry := simdParser.ParseLine([]byte(testLogLine))
  307. if simdEntry == nil {
  308. t.Fatal("SIMD parser returned nil")
  309. }
  310. // Compare results
  311. standardEntry := standardResult.Entries[0]
  312. optimizedEntry := optimizedResult.Entries[0]
  313. // Verify consistency across all methods
  314. if standardEntry.IP != optimizedEntry.IP || standardEntry.IP != simdEntry.IP {
  315. t.Errorf("IP inconsistency: standard=%s, optimized=%s, simd=%s",
  316. standardEntry.IP, optimizedEntry.IP, simdEntry.IP)
  317. }
  318. if standardEntry.Status != optimizedEntry.Status || standardEntry.Status != simdEntry.Status {
  319. t.Errorf("Status inconsistency: standard=%d, optimized=%d, simd=%d",
  320. standardEntry.Status, optimizedEntry.Status, simdEntry.Status)
  321. }
  322. t.Logf("All parsing methods produce consistent results: IP=%s, Status=%d",
  323. standardEntry.IP, standardEntry.Status)
  324. })
  325. }
  326. // generateIntegrationTestData creates realistic test data for integration testing
  327. func generateIntegrationTestData(lines int) string {
  328. var builder strings.Builder
  329. builder.Grow(lines * 200) // Pre-allocate space
  330. ips := []string{"127.0.0.1", "192.168.1.100", "10.0.0.50", "203.0.113.195", "172.16.0.25"}
  331. methods := []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}
  332. paths := []string{"/", "/index.html", "/api/data", "/styles/main.css", "/js/app.js", "/api/users", "/login", "/dashboard"}
  333. statuses := []int{200, 201, 204, 301, 302, 400, 401, 403, 404, 429, 500, 502, 503}
  334. userAgents := []string{
  335. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
  336. "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
  337. "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
  338. "curl/7.68.0",
  339. "PostmanRuntime/7.29.0",
  340. "Integration-Test/1.0",
  341. }
  342. baseTime := time.Date(2025, 9, 6, 10, 0, 0, 0, time.UTC).Unix()
  343. for i := 0; i < lines; i++ {
  344. ip := ips[i%len(ips)]
  345. timestamp := time.Unix(baseTime+int64(i*60), 0).Format("02/Jan/2006:15:04:05 -0700")
  346. method := methods[i%len(methods)]
  347. path := paths[i%len(paths)]
  348. status := statuses[i%len(statuses)]
  349. size := 1000 + (i % 10000)
  350. userAgent := userAgents[i%len(userAgents)]
  351. line := ip + ` - - [` + timestamp + `] "` + method + ` ` + path + ` HTTP/1.1" ` +
  352. string(rune('0'+status/100)) + string(rune('0'+(status/10)%10)) + string(rune('0'+status%10)) + ` ` +
  353. string(rune('0'+size/10000)) + string(rune('0'+(size/1000)%10)) +
  354. string(rune('0'+(size/100)%10)) + string(rune('0'+(size/10)%10)) + string(rune('0'+size%10)) +
  355. ` "https://example.com" "` + userAgent + `"`
  356. builder.WriteString(line)
  357. if i < lines-1 {
  358. builder.WriteString("\n")
  359. }
  360. }
  361. return builder.String()
  362. }
  363. // mockGeoIPService provides mock geo IP functionality
  364. type mockGeoIPService struct{}
  365. func (m *mockGeoIPService) Search(ip string) (*parser.GeoLocation, error) {
  366. return &parser.GeoLocation{
  367. CountryCode: "US",
  368. RegionCode: "CA",
  369. Province: "California",
  370. City: "San Francisco",
  371. }, nil
  372. }