package main import ( "fmt" "log" "math/rand" "os" "path/filepath" "runtime" "runtime/pprof" "strings" "time" "github.com/freeflowuniverse/heroagent/pkg/heroservices/billing/models" ) const ( numUsers = 1000 numAccounts = 3000 // Average 3 accounts per user numTransactions = 10000 // Reduced for quicker testing currencies = "USD,EUR,GBP,JPY,CNY,AUD,CAD,CHF,HKD,SGD" accountTypes = "Checking,Savings,Investment,Retirement,Business,Credit,Loan,Mortgage,Travel,Emergency" companies = "Apple,Google,Microsoft,Amazon,Facebook,Tesla,Walmart,Target,Costco,HomeDepot,Lowes,BestBuy,Starbucks,McDonalds,Chipotle,Nike,Adidas,Uber,Lyft,Airbnb" firstNames = "John,Jane,Michael,Emily,David,Sarah,Robert,Jennifer,William,Elizabeth,James,Linda,Richard,Barbara,Joseph,Susan,Thomas,Jessica,Charles,Mary" lastNames = "Smith,Johnson,Williams,Jones,Brown,Davis,Miller,Wilson,Moore,Taylor,Anderson,Thomas,Jackson,White,Harris,Martin,Thompson,Garcia,Martinez,Robinson" ) var ( currencyList []string accountList []string companyList []string firstNameList []string lastNameList []string ) func init() { // Initialize random seed rand.Seed(time.Now().UnixNano()) // Split the constant strings into slices currencyList = strings.Split(currencies, ",") accountList = strings.Split(accountTypes, ",") companyList = strings.Split(companies, ",") firstNameList = strings.Split(firstNames, ",") lastNameList = strings.Split(lastNames, ",") } // generateRandomName generates a random user name func generateRandomName() string { firstName := firstNameList[rand.Intn(len(firstNameList))] lastName := lastNameList[rand.Intn(len(lastNameList))] // Add timestamp and random number to ensure uniqueness return fmt.Sprintf("%s_%s_%d_%d", firstName, lastName, time.Now().UnixNano()%1000000, rand.Intn(1000)) } // generateRandomAccountName generates a random account name func generateRandomAccountName() string { accountType := accountList[rand.Intn(len(accountList))] // 30% chance to add a company name if rand.Intn(100) < 30 { company := companyList[rand.Intn(len(companyList))] return fmt.Sprintf("%s %s", company, accountType) } return accountType } // generateRandomCurrency returns a random currency code func generateRandomCurrency() string { return currencyList[rand.Intn(len(currencyList))] } // generateRandomAmount generates a random amount between min and max func generateRandomAmount(min, max float64) float64 { return min + rand.Float64()*(max-min) } // generateRandomComment generates a realistic transaction comment func generateRandomComment(fromAccount, toAccount *models.Account, amount float64) string { commentTypes := []string{ "Payment", "Transfer", "Deposit", "Withdrawal", "Refund", "Purchase", "Subscription", "Salary", "Dividend", "Interest", } commentType := commentTypes[rand.Intn(len(commentTypes))] switch commentType { case "Payment": return fmt.Sprintf("Payment to %s", toAccount.Name) case "Transfer": return fmt.Sprintf("Transfer to %s", toAccount.Name) case "Deposit": return fmt.Sprintf("Deposit to %s", toAccount.Name) case "Withdrawal": return fmt.Sprintf("Withdrawal from %s", fromAccount.Name) case "Refund": return fmt.Sprintf("Refund from %s", fromAccount.Name) case "Purchase": company := companyList[rand.Intn(len(companyList))] return fmt.Sprintf("Purchase at %s", company) case "Subscription": company := companyList[rand.Intn(len(companyList))] return fmt.Sprintf("%s subscription", company) case "Salary": company := companyList[rand.Intn(len(companyList))] return fmt.Sprintf("Salary from %s", company) case "Dividend": company := companyList[rand.Intn(len(companyList))] return fmt.Sprintf("Dividend from %s", company) case "Interest": return fmt.Sprintf("Interest payment %.2f%%", rand.Float64()*5) default: return fmt.Sprintf("Transfer of %.2f %s", amount, fromAccount.Currency) } } func main() { // Create a temporary directory for the test tempDir, err := os.MkdirTemp("", "billing-perf-test-*") if err != nil { log.Fatalf("Failed to create temp directory: %v", err) } defer os.RemoveAll(tempDir) // Create subdirectories dbPath := filepath.Join(tempDir, "db") metaPath := filepath.Join(tempDir, "meta") if err := os.MkdirAll(dbPath, 0755); err != nil { log.Fatalf("Failed to create db directory: %v", err) } if err := os.MkdirAll(metaPath, 0755); err != nil { log.Fatalf("Failed to create meta directory: %v", err) } // Set environment variables to use the temp directory originalHome := os.Getenv("HOME") os.Setenv("HOME", tempDir) defer os.Setenv("HOME", originalHome) // Start CPU profiling cpuProfile, err := os.Create("cpu_profile.prof") if err != nil { log.Fatalf("Failed to create CPU profile: %v", err) } pprof.StartCPUProfile(cpuProfile) defer pprof.StopCPUProfile() // Create a new database store fmt.Println("Creating database store...") startTime := time.Now() dbStore, err := models.NewDBStore() if err != nil { log.Fatalf("Failed to create database store: %v", err) } defer dbStore.Close() fmt.Printf("Database store created in %v\n", time.Since(startTime)) // Create user, account, and transaction stores userStore := &models.UserStore{Store: dbStore} accountStore := models.NewAccountStore(dbStore) transactionStore := models.NewTransactionStore(dbStore) // Create users fmt.Printf("Creating %d users...\n", numUsers) startTime = time.Now() users := make([]*models.User, numUsers) for i := 0; i < numUsers; i++ { user := &models.User{ Key: generateRandomName(), Accounts: []uint32{}, } if err := userStore.Save(user); err != nil { log.Fatalf("Failed to save user %d: %v", i, err) } users[i] = user if (i+1)%100 == 0 { fmt.Printf("Created %d users...\n", i+1) } } fmt.Printf("Created %d users in %v\n", numUsers, time.Since(startTime)) // Create accounts fmt.Printf("Creating %d accounts...\n", numAccounts) startTime = time.Now() accounts := make([]*models.Account, numAccounts) for i := 0; i < numAccounts; i++ { // Assign to a random user userIndex := rand.Intn(numUsers) user := users[userIndex] account := &models.Account{ UserID: user.ID, Name: generateRandomAccountName(), Currency: generateRandomCurrency(), Amount: generateRandomAmount(1000, 10000), } if err := accountStore.Save(account); err != nil { log.Fatalf("Failed to save account %d: %v", i, err) } accounts[i] = account if (i+1)%300 == 0 { fmt.Printf("Created %d accounts...\n", i+1) } } fmt.Printf("Created %d accounts in %v\n", numAccounts, time.Since(startTime)) // Create transactions fmt.Printf("Creating %d transactions...\n", numTransactions) startTime = time.Now() var memStats runtime.MemStats for i := 0; i < numTransactions; i++ { // Select random source and destination accounts fromIndex := rand.Intn(numAccounts) var toIndex int for { toIndex = rand.Intn(numAccounts) if toIndex != fromIndex { break } } fromAccount := accounts[fromIndex] toAccount := accounts[toIndex] // Generate a realistic amount (usually smaller than the available balance) maxAmount := fromAccount.Amount * 0.2 if maxAmount < 10 { maxAmount = 10 } amount := generateRandomAmount(1, maxAmount) transaction := &models.Transaction{ From: fromAccount.ID, To: toAccount.ID, Comment: generateRandomComment(fromAccount, toAccount, amount), Amount: amount, } if err := transactionStore.Save(transaction); err != nil { log.Fatalf("Failed to save transaction %d: %v", i, err) } // Update our local copy of the accounts fromAccount.Amount -= amount toAccount.Amount += amount if (i+1)%1000 == 0 { elapsed := time.Since(startTime) transPerSec := float64(i+1) / elapsed.Seconds() // Collect memory stats runtime.ReadMemStats(&memStats) fmt.Printf("Created %d transactions (%.2f/sec), Memory: %.2f MB\n", i+1, transPerSec, float64(memStats.Alloc)/1024/1024) } } totalTime := time.Since(startTime) transPerSec := float64(numTransactions) / totalTime.Seconds() fmt.Printf("Created %d transactions in %v (%.2f transactions/sec)\n", numTransactions, totalTime, transPerSec) // Memory profile memProfile, err := os.Create("mem_profile.prof") if err != nil { log.Fatalf("Failed to create memory profile: %v", err) } runtime.GC() // Run garbage collection before taking memory profile if err := pprof.WriteHeapProfile(memProfile); err != nil { log.Fatalf("Failed to write memory profile: %v", err) } memProfile.Close() // Final memory stats runtime.ReadMemStats(&memStats) fmt.Printf("\nFinal Memory Stats:\n") fmt.Printf("Alloc: %.2f MB\n", float64(memStats.Alloc)/1024/1024) fmt.Printf("TotalAlloc: %.2f MB\n", float64(memStats.TotalAlloc)/1024/1024) fmt.Printf("Sys: %.2f MB\n", float64(memStats.Sys)/1024/1024) fmt.Printf("NumGC: %d\n", memStats.NumGC) // Test retrieving random transactions fmt.Println("\nTesting random transaction retrieval...") startTime = time.Now() for i := 0; i < 1000; i++ { txID := rand.Uint32()%uint32(numTransactions) + 1 _, err := transactionStore.GetByID(txID) if err != nil { // Skip errors for non-existent transactions continue } } fmt.Printf("Retrieved 1000 random transactions in %v\n", time.Since(startTime)) // Test retrieving random accounts fmt.Println("\nTesting random account retrieval...") startTime = time.Now() for i := 0; i < 1000; i++ { accountID := rand.Uint32()%uint32(numAccounts) + 1 _, err := accountStore.GetByID(accountID) if err != nil { // Skip errors for non-existent accounts continue } } fmt.Printf("Retrieved 1000 random accounts in %v\n", time.Since(startTime)) // Test retrieving random users fmt.Println("\nTesting random user retrieval...") startTime = time.Now() for i := 0; i < 1000; i++ { userID := rand.Uint32()%uint32(numUsers) + 1 _, err := userStore.GetByID(userID) if err != nil { // Skip errors for non-existent users continue } } fmt.Printf("Retrieved 1000 random users in %v\n", time.Since(startTime)) fmt.Println("\nPerformance test completed successfully!") fmt.Printf("CPU profile saved to cpu_profile.prof\n") fmt.Printf("Memory profile saved to mem_profile.prof\n") fmt.Printf("To analyze profiles, run: go tool pprof cpu_profile.prof\n") }