- Core library with SVG and PNG generation - CLI tool with generate and batch commands - Cross-platform path handling for Windows compatibility - Comprehensive test suite with integration tests
252 lines
6.5 KiB
Go
252 lines
6.5 KiB
Go
// Package main demonstrates concurrent usage patterns for the go-jdenticon library.
|
|
// This example shows safe and efficient ways to generate identicons from multiple goroutines.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ungluedlabs/go-jdenticon/jdenticon"
|
|
)
|
|
|
|
func main() {
|
|
fmt.Println("Go Jdenticon - Concurrent Usage Examples")
|
|
fmt.Println("========================================")
|
|
|
|
// Example 1: Using package-level functions (simplest)
|
|
fmt.Println("\n1. Package-level functions (uses singleton generator)")
|
|
demonstratePackageLevelConcurrency()
|
|
|
|
// Example 2: Shared generator instance (recommended for performance)
|
|
fmt.Println("\n2. Shared generator instance (optimal performance)")
|
|
demonstrateSharedGenerator()
|
|
|
|
// Example 3: Cache performance monitoring
|
|
fmt.Println("\n3. Cache performance monitoring")
|
|
demonstrateCacheMonitoring()
|
|
|
|
// Example 4: High-throughput concurrent generation
|
|
fmt.Println("\n4. High-throughput concurrent generation")
|
|
demonstrateHighThroughput()
|
|
}
|
|
|
|
// demonstratePackageLevelConcurrency shows the simplest concurrent usage pattern
|
|
func demonstratePackageLevelConcurrency() {
|
|
userEmails := []string{
|
|
"alice@example.com",
|
|
"bob@example.com",
|
|
"charlie@example.com",
|
|
"diana@example.com",
|
|
"eve@example.com",
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for i, email := range userEmails {
|
|
wg.Add(1)
|
|
go func(id int, userEmail string) {
|
|
defer wg.Done()
|
|
|
|
// Safe: Package-level functions use internal singleton
|
|
icon, err := jdenticon.Generate(context.Background(), userEmail, 64)
|
|
if err != nil {
|
|
log.Printf("Worker %d failed to generate icon: %v", id, err)
|
|
return
|
|
}
|
|
|
|
// Icons are immutable and safe to use concurrently
|
|
svg, err := icon.ToSVG()
|
|
if err != nil {
|
|
log.Printf("Worker %d failed to generate SVG: %v", id, err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf(" Worker %d: Generated %d-byte SVG for %s\n",
|
|
id, len(svg), userEmail)
|
|
}(i, email)
|
|
}
|
|
|
|
wg.Wait()
|
|
fmt.Println(" ✓ All workers completed successfully")
|
|
}
|
|
|
|
// demonstrateSharedGenerator shows optimal performance pattern with shared generator
|
|
func demonstrateSharedGenerator() {
|
|
// Create custom configuration
|
|
config, err := jdenticon.Configure(
|
|
jdenticon.WithColorSaturation(0.8),
|
|
jdenticon.WithPadding(0.1),
|
|
jdenticon.WithHueRestrictions([]float64{120, 180, 240}), // Blue/green theme
|
|
)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create config: %v", err)
|
|
}
|
|
|
|
// Create generator with larger cache for concurrent workload
|
|
generator, err := jdenticon.NewGeneratorWithConfig(config, 1000)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create generator: %v", err)
|
|
}
|
|
|
|
const numWorkers = 10
|
|
const iconsPerWorker = 5
|
|
|
|
var wg sync.WaitGroup
|
|
start := time.Now()
|
|
|
|
for workerID := 0; workerID < numWorkers; workerID++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
|
|
for i := 0; i < iconsPerWorker; i++ {
|
|
userID := fmt.Sprintf("user-%d-%d@company.com", id, i)
|
|
|
|
// Safe: Multiple goroutines can use the same generator
|
|
icon, err := generator.Generate(context.Background(), userID, 96)
|
|
if err != nil {
|
|
log.Printf("Worker %d failed to generate icon %d: %v", id, i, err)
|
|
continue
|
|
}
|
|
|
|
// Generate both formats concurrently on the same icon
|
|
var pngData []byte
|
|
var svgData string
|
|
var formatWg sync.WaitGroup
|
|
|
|
formatWg.Add(2)
|
|
go func() {
|
|
defer formatWg.Done()
|
|
pngData, _ = icon.ToPNG()
|
|
}()
|
|
go func() {
|
|
defer formatWg.Done()
|
|
svgData, _ = icon.ToSVG()
|
|
}()
|
|
formatWg.Wait()
|
|
|
|
fmt.Printf(" Worker %d: Generated PNG (%d bytes) and SVG (%d bytes) for %s\n",
|
|
id, len(pngData), len(svgData), userID)
|
|
}
|
|
}(workerID)
|
|
}
|
|
|
|
wg.Wait()
|
|
duration := time.Since(start)
|
|
totalIcons := numWorkers * iconsPerWorker
|
|
|
|
fmt.Printf(" ✓ Generated %d icons in %v (%.0f icons/sec)\n",
|
|
totalIcons, duration, float64(totalIcons)/duration.Seconds())
|
|
}
|
|
|
|
// demonstrateCacheMonitoring shows how to monitor cache performance
|
|
func demonstrateCacheMonitoring() {
|
|
generator, err := jdenticon.NewGeneratorWithConfig(jdenticon.DefaultConfig(), 100)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create generator: %v", err)
|
|
}
|
|
|
|
// Generate some icons to populate cache
|
|
testUsers := []string{
|
|
"user1@test.com", "user2@test.com", "user3@test.com",
|
|
"user4@test.com", "user5@test.com",
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// First pass: populate cache
|
|
fmt.Println(" Populating cache...")
|
|
for _, user := range testUsers {
|
|
wg.Add(1)
|
|
go func(u string) {
|
|
defer wg.Done()
|
|
_, _ = generator.Generate(context.Background(), u, 64)
|
|
}(user)
|
|
}
|
|
wg.Wait()
|
|
|
|
hits1, misses1 := generator.GetCacheMetrics()
|
|
fmt.Printf(" After first pass - Hits: %d, Misses: %d\n", hits1, misses1)
|
|
|
|
// Second pass: should hit cache
|
|
fmt.Println(" Requesting same icons (should hit cache)...")
|
|
for _, user := range testUsers {
|
|
wg.Add(1)
|
|
go func(u string) {
|
|
defer wg.Done()
|
|
_, _ = generator.Generate(context.Background(), u, 64)
|
|
}(user)
|
|
}
|
|
wg.Wait()
|
|
|
|
hits2, misses2 := generator.GetCacheMetrics()
|
|
fmt.Printf(" After second pass - Hits: %d, Misses: %d\n", hits2, misses2)
|
|
|
|
if hits2 > hits1 {
|
|
ratio := float64(hits2) / float64(hits2+misses2) * 100
|
|
fmt.Printf(" ✓ Cache hit ratio: %.1f%%\n", ratio)
|
|
}
|
|
}
|
|
|
|
// demonstrateHighThroughput shows high-performance concurrent generation
|
|
func demonstrateHighThroughput() {
|
|
generator, err := jdenticon.NewGeneratorWithConfig(jdenticon.DefaultConfig(), 2000)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create generator: %v", err)
|
|
}
|
|
|
|
const numWorkers = 20
|
|
const duration = 2 * time.Second
|
|
|
|
var wg sync.WaitGroup
|
|
stopChan := make(chan struct{})
|
|
|
|
// Start timer
|
|
go func() {
|
|
time.Sleep(duration)
|
|
close(stopChan)
|
|
}()
|
|
|
|
start := time.Now()
|
|
|
|
// Launch workers
|
|
for i := 0; i < numWorkers; i++ {
|
|
wg.Add(1)
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
|
|
count := 0
|
|
for {
|
|
select {
|
|
case <-stopChan:
|
|
fmt.Printf(" Worker %d generated %d icons\n", workerID, count)
|
|
return
|
|
default:
|
|
userID := fmt.Sprintf("load-test-user-%d-%d", workerID, count)
|
|
_, err := generator.Generate(context.Background(), userID, 32)
|
|
if err != nil {
|
|
log.Printf("Generation failed: %v", err)
|
|
continue
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
actualDuration := time.Since(start)
|
|
|
|
hits, misses := generator.GetCacheMetrics()
|
|
total := hits + misses
|
|
throughput := float64(total) / actualDuration.Seconds()
|
|
|
|
fmt.Printf(" ✓ Generated %d icons in %v (%.0f icons/sec)\n",
|
|
total, actualDuration, throughput)
|
|
fmt.Printf(" ✓ Cache performance - Hits: %d, Misses: %d (%.1f%% hit rate)\n",
|
|
hits, misses, float64(hits)/float64(total)*100)
|
|
}
|