performance_bench_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. package nginx_log
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "math/rand"
  7. "os"
  8. "path/filepath"
  9. "runtime"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/dgraph-io/ristretto/v2"
  14. )
  15. // Test data generators for realistic nginx log simulation
  16. var (
  17. ips = []string{
  18. "192.168.1.1", "10.0.0.1", "172.16.0.1", "203.0.113.1", "198.51.100.1",
  19. "192.168.2.100", "10.10.10.10", "172.31.255.255", "8.8.8.8", "1.1.1.1",
  20. }
  21. userAgents = []string{
  22. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  23. "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  24. "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
  25. "Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
  26. "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0",
  27. }
  28. methods = []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}
  29. paths = []string{
  30. "/api/v1/users", "/api/v1/orders", "/api/v1/products", "/api/v1/auth/login",
  31. "/static/js/app.js", "/static/css/main.css", "/images/logo.png",
  32. "/admin/dashboard", "/admin/users", "/admin/settings",
  33. "/health", "/metrics", "/favicon.ico", "/robots.txt",
  34. }
  35. statuses = []int{200, 201, 400, 401, 403, 404, 500, 502, 503}
  36. referers = []string{
  37. "https://example.com", "https://google.com", "https://github.com",
  38. "-", "https://stackoverflow.com", "https://reddit.com",
  39. }
  40. )
  41. func generateRandomLogLine(timestamp time.Time) string {
  42. ip := ips[rand.Intn(len(ips))]
  43. method := methods[rand.Intn(len(methods))]
  44. path := paths[rand.Intn(len(paths))]
  45. if rand.Float32() < 0.3 {
  46. path += fmt.Sprintf("/%d", rand.Intn(10000))
  47. }
  48. status := statuses[rand.Intn(len(statuses))]
  49. size := rand.Intn(50000) + 100
  50. referer := referers[rand.Intn(len(referers))]
  51. userAgent := userAgents[rand.Intn(len(userAgents))]
  52. timeStr := timestamp.Format("02/Jan/2006:15:04:05 -0700")
  53. return fmt.Sprintf(`%s - - [%s] "%s %s HTTP/1.1" %d %d "%s" "%s"`,
  54. ip, timeStr, method, path, status, size, referer, userAgent)
  55. }
  56. func generateLogFile(filePath string, count int) error {
  57. file, err := os.Create(filePath)
  58. if err != nil {
  59. return err
  60. }
  61. defer file.Close()
  62. writer := bufio.NewWriter(file)
  63. defer writer.Flush()
  64. baseTime := time.Now().Add(-24 * time.Hour)
  65. for i := 0; i < count; i++ {
  66. timestamp := baseTime.Add(time.Duration(i) * time.Second / time.Duration(count))
  67. line := generateRandomLogLine(timestamp)
  68. if _, err := writer.WriteString(line + "\n"); err != nil {
  69. return err
  70. }
  71. if i%100000 == 0 {
  72. writer.Flush()
  73. }
  74. }
  75. return nil
  76. }
  77. func BenchmarkLogGeneration_1M(b *testing.B) {
  78. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  79. if err != nil {
  80. b.Fatal(err)
  81. }
  82. defer os.RemoveAll(tempDir)
  83. b.ResetTimer()
  84. for i := 0; i < b.N; i++ {
  85. logFile := filepath.Join(tempDir, fmt.Sprintf("access_%d.log", i))
  86. err := generateLogFile(logFile, 1000000)
  87. if err != nil {
  88. b.Fatal(err)
  89. }
  90. }
  91. }
  92. func BenchmarkLogParsing_OptimizedBatch(b *testing.B) {
  93. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  94. if err != nil {
  95. b.Fatal(err)
  96. }
  97. defer os.RemoveAll(tempDir)
  98. logFile := filepath.Join(tempDir, "access.log")
  99. err = generateLogFile(logFile, 1000000)
  100. if err != nil {
  101. b.Fatal(err)
  102. }
  103. parser := NewOptimizedLogParser(NewSimpleUserAgentParser())
  104. b.ResetTimer()
  105. b.ReportAllocs()
  106. for i := 0; i < b.N; i++ {
  107. file, err := os.Open(logFile)
  108. if err != nil {
  109. b.Fatal(err)
  110. }
  111. scanner := bufio.NewScanner(file)
  112. scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
  113. count := 0
  114. for scanner.Scan() {
  115. line := scanner.Text()
  116. if strings.TrimSpace(line) == "" {
  117. continue
  118. }
  119. _, err := parser.ParseLine(line)
  120. if err != nil {
  121. continue
  122. }
  123. count++
  124. }
  125. file.Close()
  126. }
  127. }
  128. func BenchmarkIndexing_LargeDataset(b *testing.B) {
  129. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  130. if err != nil {
  131. b.Fatal(err)
  132. }
  133. defer os.RemoveAll(tempDir)
  134. logFile := filepath.Join(tempDir, "access.log")
  135. err = generateLogFile(logFile, 1000000)
  136. if err != nil {
  137. b.Fatal(err)
  138. }
  139. b.ResetTimer()
  140. for i := 0; i < b.N; i++ {
  141. b.StopTimer()
  142. indexPath := filepath.Join(tempDir, fmt.Sprintf("index_%d", i))
  143. index, err := createOrOpenIndex(indexPath)
  144. if err != nil {
  145. b.Fatal(err)
  146. }
  147. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  148. NumCounters: 1e7,
  149. MaxCost: 1 << 30,
  150. BufferItems: 64,
  151. })
  152. if err != nil {
  153. b.Fatal(err)
  154. }
  155. indexer := &LogIndexer{
  156. index: index,
  157. indexPath: indexPath,
  158. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  159. logPaths: make(map[string]*LogFileInfo),
  160. indexBatch: 50000,
  161. cache: cache,
  162. }
  163. err = indexer.AddLogPath(logFile)
  164. if err != nil {
  165. b.Fatal(err)
  166. }
  167. b.StartTimer()
  168. err = indexer.IndexLogFile(logFile)
  169. if err != nil {
  170. b.Fatal(err)
  171. }
  172. b.StopTimer()
  173. indexer.Close()
  174. }
  175. }
  176. func BenchmarkSearch_ComplexQueries(b *testing.B) {
  177. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  178. if err != nil {
  179. b.Fatal(err)
  180. }
  181. defer os.RemoveAll(tempDir)
  182. logFile := filepath.Join(tempDir, "access.log")
  183. err = generateLogFile(logFile, 500000)
  184. if err != nil {
  185. b.Fatal(err)
  186. }
  187. indexPath := filepath.Join(tempDir, "index")
  188. index, err := createOrOpenIndex(indexPath)
  189. if err != nil {
  190. b.Fatal(err)
  191. }
  192. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  193. NumCounters: 1e7,
  194. MaxCost: 1 << 29,
  195. BufferItems: 64,
  196. })
  197. if err != nil {
  198. b.Fatal(err)
  199. }
  200. indexer := &LogIndexer{
  201. index: index,
  202. indexPath: indexPath,
  203. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  204. logPaths: make(map[string]*LogFileInfo),
  205. indexBatch: 25000,
  206. cache: cache,
  207. }
  208. defer indexer.Close()
  209. err = indexer.AddLogPath(logFile)
  210. if err != nil {
  211. b.Fatal(err)
  212. }
  213. err = indexer.IndexLogFile(logFile)
  214. if err != nil {
  215. b.Fatal(err)
  216. }
  217. time.Sleep(2 * time.Second)
  218. queries := []*QueryRequest{
  219. {Method: "GET", Limit: 1000},
  220. {Status: []int{200, 201}, Limit: 1000},
  221. {IP: "192.168.1.1", Limit: 1000},
  222. {Path: "/api/v1/users", Limit: 1000},
  223. {Method: "POST", Status: []int{400, 401, 403}, Limit: 1000},
  224. }
  225. b.ResetTimer()
  226. b.ReportAllocs()
  227. for i := 0; i < b.N; i++ {
  228. query := queries[i%len(queries)]
  229. _, err := indexer.SearchLogs(context.Background(), query)
  230. if err != nil {
  231. b.Fatal(err)
  232. }
  233. }
  234. }
  235. func BenchmarkAnalytics_IndexStatus(b *testing.B) {
  236. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  237. if err != nil {
  238. b.Fatal(err)
  239. }
  240. defer os.RemoveAll(tempDir)
  241. logFile := filepath.Join(tempDir, "access.log")
  242. err = generateLogFile(logFile, 500000)
  243. if err != nil {
  244. b.Fatal(err)
  245. }
  246. indexPath := filepath.Join(tempDir, "index")
  247. index, err := createOrOpenIndex(indexPath)
  248. if err != nil {
  249. b.Fatal(err)
  250. }
  251. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  252. NumCounters: 1e7,
  253. MaxCost: 1 << 29,
  254. BufferItems: 64,
  255. })
  256. if err != nil {
  257. b.Fatal(err)
  258. }
  259. indexer := &LogIndexer{
  260. index: index,
  261. indexPath: indexPath,
  262. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  263. logPaths: make(map[string]*LogFileInfo),
  264. indexBatch: 25000,
  265. cache: cache,
  266. }
  267. defer indexer.Close()
  268. err = indexer.AddLogPath(logFile)
  269. if err != nil {
  270. b.Fatal(err)
  271. }
  272. err = indexer.IndexLogFile(logFile)
  273. if err != nil {
  274. b.Fatal(err)
  275. }
  276. time.Sleep(2 * time.Second)
  277. b.ResetTimer()
  278. b.ReportAllocs()
  279. for i := 0; i < b.N; i++ {
  280. _, err := indexer.GetIndexStatus()
  281. if err != nil {
  282. b.Fatal(err)
  283. }
  284. }
  285. }
  286. func BenchmarkMemoryEfficiency_LargeDataset(b *testing.B) {
  287. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  288. if err != nil {
  289. b.Fatal(err)
  290. }
  291. defer os.RemoveAll(tempDir)
  292. logFile := filepath.Join(tempDir, "access.log")
  293. b.ResetTimer()
  294. for i := 0; i < b.N; i++ {
  295. b.StopTimer()
  296. var m1, m2 runtime.MemStats
  297. runtime.GC()
  298. runtime.ReadMemStats(&m1)
  299. b.StartTimer()
  300. err := generateLogFile(logFile, 1000000)
  301. if err != nil {
  302. b.Fatal(err)
  303. }
  304. parser := NewOptimizedLogParser(NewSimpleUserAgentParser())
  305. file, err := os.Open(logFile)
  306. if err != nil {
  307. b.Fatal(err)
  308. }
  309. scanner := bufio.NewScanner(file)
  310. scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
  311. count := 0
  312. for scanner.Scan() {
  313. line := scanner.Text()
  314. if strings.TrimSpace(line) == "" {
  315. continue
  316. }
  317. _, err := parser.ParseLine(line)
  318. if err != nil {
  319. continue
  320. }
  321. count++
  322. if count%100000 == 0 {
  323. runtime.GC()
  324. }
  325. }
  326. file.Close()
  327. b.StopTimer()
  328. runtime.GC()
  329. runtime.ReadMemStats(&m2)
  330. b.ReportMetric(float64(m2.Alloc-m1.Alloc)/1024/1024, "MB/processed")
  331. b.ReportMetric(float64(count), "lines/processed")
  332. os.Remove(logFile)
  333. }
  334. }
  335. func BenchmarkConcurrentParsing_MultiCore(b *testing.B) {
  336. tempDir, err := os.MkdirTemp("", "nginx_log_bench")
  337. if err != nil {
  338. b.Fatal(err)
  339. }
  340. defer os.RemoveAll(tempDir)
  341. numWorkers := runtime.NumCPU()
  342. linesPerFile := 200000
  343. logFiles := make([]string, numWorkers)
  344. for i := 0; i < numWorkers; i++ {
  345. logFile := filepath.Join(tempDir, fmt.Sprintf("access_%d.log", i))
  346. err := generateLogFile(logFile, linesPerFile)
  347. if err != nil {
  348. b.Fatal(err)
  349. }
  350. logFiles[i] = logFile
  351. }
  352. b.ResetTimer()
  353. b.ReportAllocs()
  354. for i := 0; i < b.N; i++ {
  355. done := make(chan int, numWorkers)
  356. for j := 0; j < numWorkers; j++ {
  357. go func(fileIndex int) {
  358. parser := NewOptimizedLogParser(NewSimpleUserAgentParser())
  359. file, err := os.Open(logFiles[fileIndex])
  360. if err != nil {
  361. done <- 0
  362. return
  363. }
  364. defer file.Close()
  365. scanner := bufio.NewScanner(file)
  366. scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
  367. count := 0
  368. for scanner.Scan() {
  369. line := scanner.Text()
  370. if strings.TrimSpace(line) == "" {
  371. continue
  372. }
  373. _, err := parser.ParseLine(line)
  374. if err != nil {
  375. continue
  376. }
  377. count++
  378. }
  379. done <- count
  380. }(j)
  381. }
  382. totalProcessed := 0
  383. for j := 0; j < numWorkers; j++ {
  384. totalProcessed += <-done
  385. }
  386. b.ReportMetric(float64(totalProcessed), "total_lines_processed")
  387. }
  388. }
  389. func BenchmarkOptimizedParser_vs_Standard(b *testing.B) {
  390. logLine := `192.168.1.1 - - [25/Dec/2023:10:00:00 +0000] "GET /api/v1/users/123 HTTP/1.1" 200 1024 "https://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"`
  391. b.Run("StandardParser", func(b *testing.B) {
  392. parser := NewOptimizedLogParser(NewSimpleUserAgentParser())
  393. b.ResetTimer()
  394. b.ReportAllocs()
  395. for i := 0; i < b.N; i++ {
  396. _, _ = parser.ParseLine(logLine)
  397. }
  398. })
  399. b.Run("OptimizedParser", func(b *testing.B) {
  400. parser := NewOptimizedLogParser(NewSimpleUserAgentParser())
  401. b.ResetTimer()
  402. b.ReportAllocs()
  403. for i := 0; i < b.N; i++ {
  404. _, _ = parser.ParseLine(logLine)
  405. }
  406. })
  407. }
  408. func BenchmarkBatchIndexing_OptimizedSizes(b *testing.B) {
  409. tempDir, err := os.MkdirTemp("", "nginx_log_batch_bench")
  410. if err != nil {
  411. b.Fatal(err)
  412. }
  413. defer os.RemoveAll(tempDir)
  414. logFile := filepath.Join(tempDir, "access.log")
  415. err = generateLogFile(logFile, 1000000)
  416. if err != nil {
  417. b.Fatal(err)
  418. }
  419. batchSizes := []int{1000, 5000, 10000, 25000, 50000, 100000}
  420. for _, batchSize := range batchSizes {
  421. b.Run(fmt.Sprintf("BatchSize_%d", batchSize), func(b *testing.B) {
  422. for i := 0; i < b.N; i++ {
  423. b.StopTimer()
  424. indexPath := filepath.Join(tempDir, fmt.Sprintf("index_batch_%d_%d", batchSize, i))
  425. index, err := createOrOpenIndex(indexPath)
  426. if err != nil {
  427. b.Fatal(err)
  428. }
  429. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  430. NumCounters: 1e7,
  431. MaxCost: 1 << 28,
  432. BufferItems: 64,
  433. })
  434. if err != nil {
  435. b.Fatal(err)
  436. }
  437. indexer := &LogIndexer{
  438. index: index,
  439. indexPath: indexPath,
  440. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  441. logPaths: make(map[string]*LogFileInfo),
  442. indexBatch: batchSize,
  443. cache: cache,
  444. }
  445. err = indexer.AddLogPath(logFile)
  446. if err != nil {
  447. b.Fatal(err)
  448. }
  449. b.StartTimer()
  450. err = indexer.IndexLogFile(logFile)
  451. if err != nil {
  452. b.Fatal(err)
  453. }
  454. b.StopTimer()
  455. indexer.Close()
  456. os.RemoveAll(indexPath)
  457. }
  458. })
  459. }
  460. }
  461. func BenchmarkStreamingProcessor_HighThroughput(b *testing.B) {
  462. tempDir, err := os.MkdirTemp("", "nginx_log_streaming_bench")
  463. if err != nil {
  464. b.Fatal(err)
  465. }
  466. defer os.RemoveAll(tempDir)
  467. logFile := filepath.Join(tempDir, "access.log")
  468. err = generateLogFile(logFile, 1000000)
  469. if err != nil {
  470. b.Fatal(err)
  471. }
  472. workerCounts := []int{1, 2, 4, 8, 16}
  473. for _, workers := range workerCounts {
  474. b.Run(fmt.Sprintf("Workers_%d", workers), func(b *testing.B) {
  475. for i := 0; i < b.N; i++ {
  476. b.StopTimer()
  477. indexPath := filepath.Join(tempDir, fmt.Sprintf("index_stream_%d_%d", workers, i))
  478. index, err := createOrOpenIndex(indexPath)
  479. if err != nil {
  480. b.Fatal(err)
  481. }
  482. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  483. NumCounters: 1e7,
  484. MaxCost: 1 << 28,
  485. BufferItems: 64,
  486. })
  487. if err != nil {
  488. b.Fatal(err)
  489. }
  490. indexer := &LogIndexer{
  491. index: index,
  492. indexPath: indexPath,
  493. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  494. logPaths: make(map[string]*LogFileInfo),
  495. indexBatch: 25000,
  496. cache: cache,
  497. }
  498. processor := NewStreamingLogProcessor(indexer, 10000, workers)
  499. file, err := os.Open(logFile)
  500. if err != nil {
  501. b.Fatal(err)
  502. }
  503. b.StartTimer()
  504. err = processor.ProcessFile(file)
  505. if err != nil {
  506. b.Fatal(err)
  507. }
  508. b.StopTimer()
  509. file.Close()
  510. indexer.Close()
  511. os.RemoveAll(indexPath)
  512. }
  513. })
  514. }
  515. }
  516. func BenchmarkSearchPerformance_LargeResults(b *testing.B) {
  517. tempDir, err := os.MkdirTemp("", "nginx_log_search_bench")
  518. if err != nil {
  519. b.Fatal(err)
  520. }
  521. defer os.RemoveAll(tempDir)
  522. logFile := filepath.Join(tempDir, "access.log")
  523. err = generateLogFile(logFile, 2000000)
  524. if err != nil {
  525. b.Fatal(err)
  526. }
  527. indexPath := filepath.Join(tempDir, "index")
  528. index, err := createOrOpenIndex(indexPath)
  529. if err != nil {
  530. b.Fatal(err)
  531. }
  532. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  533. NumCounters: 1e7,
  534. MaxCost: 1 << 29,
  535. BufferItems: 64,
  536. })
  537. if err != nil {
  538. b.Fatal(err)
  539. }
  540. indexer := &LogIndexer{
  541. index: index,
  542. indexPath: indexPath,
  543. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  544. logPaths: make(map[string]*LogFileInfo),
  545. indexBatch: 50000,
  546. cache: cache,
  547. }
  548. defer indexer.Close()
  549. err = indexer.AddLogPath(logFile)
  550. if err != nil {
  551. b.Fatal(err)
  552. }
  553. err = indexer.IndexLogFile(logFile)
  554. if err != nil {
  555. b.Fatal(err)
  556. }
  557. time.Sleep(3 * time.Second)
  558. limits := []int{100, 1000, 5000, 10000, 50000}
  559. for _, limit := range limits {
  560. b.Run(fmt.Sprintf("Limit_%d", limit), func(b *testing.B) {
  561. query := &QueryRequest{
  562. Method: "GET",
  563. Limit: limit,
  564. }
  565. b.ResetTimer()
  566. b.ReportAllocs()
  567. for i := 0; i < b.N; i++ {
  568. result, err := indexer.SearchLogs(context.Background(), query)
  569. if err != nil {
  570. b.Fatal(err)
  571. }
  572. b.ReportMetric(float64(len(result.Entries)), "results_returned")
  573. }
  574. })
  575. }
  576. }
  577. func BenchmarkAnalyticsAggregation_GeoStats(b *testing.B) {
  578. tempDir, err := os.MkdirTemp("", "nginx_log_analytics_bench")
  579. if err != nil {
  580. b.Fatal(err)
  581. }
  582. defer os.RemoveAll(tempDir)
  583. logFile := filepath.Join(tempDir, "access.log")
  584. err = generateLogFile(logFile, 500000)
  585. if err != nil {
  586. b.Fatal(err)
  587. }
  588. indexPath := filepath.Join(tempDir, "index")
  589. index, err := createOrOpenIndex(indexPath)
  590. if err != nil {
  591. b.Fatal(err)
  592. }
  593. cache, err := ristretto.NewCache(&ristretto.Config[string, *CachedSearchResult]{
  594. NumCounters: 1e7,
  595. MaxCost: 1 << 29,
  596. BufferItems: 64,
  597. })
  598. if err != nil {
  599. b.Fatal(err)
  600. }
  601. statsService := NewBleveStatsService()
  602. indexer := &LogIndexer{
  603. index: index,
  604. indexPath: indexPath,
  605. parser: NewOptimizedLogParser(NewSimpleUserAgentParser()),
  606. logPaths: make(map[string]*LogFileInfo),
  607. indexBatch: 50000,
  608. cache: cache,
  609. }
  610. defer indexer.Close()
  611. statsService.SetIndexer(indexer)
  612. err = indexer.AddLogPath(logFile)
  613. if err != nil {
  614. b.Fatal(err)
  615. }
  616. err = indexer.IndexLogFile(logFile)
  617. if err != nil {
  618. b.Fatal(err)
  619. }
  620. time.Sleep(3 * time.Second)
  621. b.ResetTimer()
  622. b.ReportAllocs()
  623. for i := 0; i < b.N; i++ {
  624. _, err := statsService.GetGeoStats(context.Background(), nil, 100)
  625. if err != nil {
  626. b.Fatal(err)
  627. }
  628. }
  629. }
  630. func Benchmark100MRecords_FullPipeline(b *testing.B) {
  631. if testing.Short() {
  632. b.Skip("Skipping 100M records benchmark in short mode")
  633. }
  634. tempDir, err := os.MkdirTemp("", "nginx_log_100m_bench")
  635. if err != nil {
  636. b.Fatal(err)
  637. }
  638. defer os.RemoveAll(tempDir)
  639. b.Log("Starting 100M records benchmark...")
  640. logFile := filepath.Join(tempDir, "access_100m.log")
  641. b.ResetTimer()
  642. for i := 0; i < b.N; i++ {
  643. b.StopTimer()
  644. var m1, m2 runtime.MemStats
  645. runtime.GC()
  646. runtime.ReadMemStats(&m1)
  647. startTime := time.Now()
  648. b.Log("Phase 1: Generating 100M log records...")
  649. err := generateLogFile(logFile, 100000000)
  650. if err != nil {
  651. b.Fatal(err)
  652. }
  653. generationTime := time.Since(startTime)
  654. b.ReportMetric(generationTime.Seconds(), "generation_time_seconds")
  655. b.Logf("Generation completed in %.2f seconds", generationTime.Seconds())
  656. parseStartTime := time.Now()
  657. b.Log("Phase 2: Parsing with optimized parser...")
  658. b.StartTimer()
  659. parser := NewOptimizedLogParser(NewSimpleUserAgentParser())
  660. file, err := os.Open(logFile)
  661. if err != nil {
  662. b.Fatal(err)
  663. }
  664. scanner := bufio.NewScanner(file)
  665. scanner.Buffer(make([]byte, 0, 256*1024), 4096*1024)
  666. count := 0
  667. batchSize := 500000
  668. for scanner.Scan() {
  669. line := scanner.Text()
  670. if strings.TrimSpace(line) == "" {
  671. continue
  672. }
  673. _, err := parser.ParseLine(line)
  674. if err != nil {
  675. continue
  676. }
  677. count++
  678. if count%batchSize == 0 {
  679. runtime.GC()
  680. if count%(batchSize*10) == 0 {
  681. b.Logf("Processed %d records (%.1f%% complete)", count, float64(count)/100000000*100)
  682. }
  683. }
  684. }
  685. file.Close()
  686. b.StopTimer()
  687. parseTime := time.Since(parseStartTime)
  688. runtime.GC()
  689. runtime.ReadMemStats(&m2)
  690. b.ReportMetric(parseTime.Seconds(), "parse_time_seconds")
  691. b.ReportMetric(float64(count), "total_records_processed")
  692. b.ReportMetric(float64(count)/parseTime.Seconds(), "records_per_second")
  693. b.ReportMetric(float64(m2.Alloc-m1.Alloc)/1024/1024, "peak_memory_MB")
  694. b.Logf("Parse completed: %d records in %.2f seconds (%.0f records/sec)",
  695. count, parseTime.Seconds(), float64(count)/parseTime.Seconds())
  696. b.Logf("Peak memory usage: %.2f MB", float64(m2.Alloc-m1.Alloc)/1024/1024)
  697. os.Remove(logFile)
  698. }
  699. }