heroagent/pkg/heroservices/billing/models/cmd/main.go
2025-04-23 04:18:28 +02:00

349 lines
10 KiB
Go

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")
}