A high-performance Go-based simulation tool that mimics a day in the life of an order processing system. The simulator creates and manages 200+ orders through their complete lifecycle using 5 primary APIs, with configurable batch processing, parallel execution, and timing controls. Each order includes a unique GeoJSON polyline representing a geographical path, generated using a boundary-constrained zigzag crawl algorithm.
- Payload Pre-Generation: All order payloads are generated before processing begins
- Geographical Path Generation: Each order includes a unique GeoJSON polyline with boundary constraints
- Zigzag Crawl Pattern: Non-overlapping paths distributed using intelligent staircase algorithm
- Boundary Validation: Point-in-polygon algorithm ensures all paths stay within configured area
- Type-Based Routing: Orders are processed differently based on type (activate vs accepted)
- Async Termination: Non-blocking cancel/end operations using channel-based architecture
- Parallel Batch Processing: Configurable number of parallel batches with sequential order processing within each batch
- GeoJSON Export: Visual verification via geojson.io compatible output files
- Robust Error Handling: Exponential backoff, retries, and comprehensive error recovery
- Graceful Shutdown: Proper signal handling and context-based cancellation
- Structured Logging: JSON-formatted logs with configurable log levels
- Metrics Tracking: Real-time statistics and detailed reporting
gameday-sim/
├── main.go # Entry point and orchestration
├── config.yaml # Configuration file
├── internal/
│ ├── api/ # API client implementation
│ │ ├── client.go # HTTP client with retry logic
│ │ ├── models.go # Request/response models
│ │ └── endpoints.go # API endpoint methods
│ ├── simulator/ # Core simulation logic
│ │ ├── batch.go # Batch processing
│ │ └── order.go # Order lifecycle management
│ ├── payload/ # Payload generation and distribution
│ │ ├── generator.go # Payload generator
│ │ ├── distributor.go # Batch distributor
│ │ └── types.go # Data types
│ ├── config/ # Configuration management
│ │ └── config.go # Config parsing and validation
│ └── utils/ # Utilities
│ ├── logger.go # Structured logging
│ └── metrics.go # Metrics tracking
└── tests/ # Unit tests
└── payload_test.go
Activate Type Orders:
- CREATE → 2. GET (poll until accepted) → 3. ACTIVATE → 4. Schedule END (async) → ProcessOrder returns
- Background worker processes END operation asynchronously
Accepted Type Orders:
- CREATE → 2. GET (poll until accepted) → 2. Schedule CANCEL (async) → ProcessOrder returns
- Background worker processes CANCEL operation asynchronously
Order States:
StateCreated→StateAccepted→StateActivated→StatePendingEnd→StateEndedStateCreated→StateAccepted→StatePendingCancel→StateCancelledStateFailed(on any error)
# 1. Build the application
go build -o gameday-sim .
# 2. Edit config.yaml with your settings
# - Set API endpoints
# - Configure geographical boundary
# - Adjust timing parameters
# 3. Run the simulator
./gameday-sim
# 4. View generated paths
# Open logs/geojsons/payloads_*.json in geojson.io- Go 1.21 or higher
- Access to the target API endpoints
# Clone the repository
cd gameday-sim
# Install dependencies
go mod download
# Build the application
go build -o gameday-sim .Edit config.yaml to customize the simulation:
simulation:
totalOrders: 200 # Total number of orders to create
batchSize: 20 # Orders per batch
parallelBatches: 10 # Number of batches to run in parallel
activatedCount: 170 # Number of orders to activate (rest will be accepted only)
payload:
location: "US-EAST-1"
pocOrder: "POC-2024-001"
orderNumberPrefix: "ORD-2024-"
customFields:
priority: "normal"
source: "simulator"
basePolyline: # Template polyline for path generation
coordinates:
- [-96.79943798188481, 32.795102753983585]
- [-96.79927289435462, 32.78885767285452]
- [-96.79811728164334, 32.780252620552886]
delta: # Spacing between paths
longitude: 0.001 # Horizontal spacing (east-west)
latitude: 0.001 # Additional vertical spacing between rows
boundary: # Polygon boundary (GeoJSON format)
coordinates:
- - [-96.80726593015929, 32.796582675082036]
- [-96.80726593015929, 32.7781210082299]
- [-96.78175523630775, 32.7781210082299]
- [-96.78175523630775, 32.796582675082036]
- [-96.80726593015929, 32.796582675082036]
intervals:
betweenCreates: 2s # Wait time between creating orders in a batch
afterCreateBeforeGet: 5s # Wait before first status check
betweenGetPolls: 3s # Polling interval for status checks
beforeActivate: 2s # Wait before activation
beforeCancel: 30s # Wait before scheduling cancellation
beforeEnd: 60s # Wait before scheduling end operation
api:
baseUrl: "https://api.example.com"
timeout: 30s
retryMax: 3
retryBackoff: 2s
cleanup:
cancelTimeout: 300s # Timeout for order acceptance
endTimeout: 600s # Timeout for cleanup operations
checkInterval: 10s # Interval for cleanup checks| Parameter | Description | Default |
|---|---|---|
totalOrders |
Total number of orders to simulate | 200 |
batchSize |
Number of orders per batch | 20 |
parallelBatches |
Number of batches running concurrently | 10 |
activatedCount |
Orders that will be activated (must be ≤ totalOrders) | 170 |
| Parameter | Description | Format |
|---|---|---|
basePolyline.coordinates |
Template polyline for path generation | Array of [lng, lat] pairs |
delta.longitude |
Horizontal spacing between paths (degrees) | Float (e.g., 0.001) |
delta.latitude |
Additional vertical spacing between rows (degrees) | Float (e.g., 0.001) |
boundary.coordinates |
Polygon boundary constraint (GeoJSON Polygon) | Array of rings, each ring is array of [lng, lat] |
Geographical Behavior:
- Paths are generated in a zigzag pattern: left-to-right on row 0, right-to-left on row 1, etc.
- Each row is stacked vertically with spacing = (polyline_height + delta.latitude)
- All polyline points must fall within the boundary polygon
- Point-in-polygon validation uses ray-casting algorithm
# Run with default config.yaml
./gameday-sim
# Run with custom config file
./gameday-sim -config /path/to/config.yaml
# Run with different log level
./gameday-sim -log-level DEBUG-config: Path to configuration file (default: "config.yaml")-log-level: Log level - DEBUG, INFO, WARN, ERROR (default: "INFO")
================================================================================
SIMULATION RESULTS
================================================================================
Total Orders: 200
Successful Orders: 198
Failed Orders: 2
Activated Orders: 170
Ended Orders: 165
Cancelled Orders: 28
Pending End: 5
Pending Cancel: 0
Total Batches: 10
Total Duration: 5m23.456s
Avg Order Duration: 1m34.123s
GeoJSON Output: logs/geojsons/payloads_20260103_162230.json
================================================================================
- Pre-generates all order payloads at initialization
- Assigns unique order numbers (e.g., "ORD-2024-000001")
- Generates unique GeoJSON polyline for each order:
- Applies zigzag crawl pattern within boundary polygon
- Ensures non-overlapping paths using delta spacing
- Validates all coordinates are within boundary
- Distributes order types based on
activatedCount - Optionally shuffles payloads for random distribution across batches
- Batches run in parallel (controlled by
parallelBatches) - Orders within a batch execute sequentially
- Each batch uses a goroutine with proper synchronization
- Semaphore pattern limits concurrent batches
The simulator uses an intelligent algorithm to generate unique, non-overlapping paths:
Zigzag Crawl Pattern:
Row 0: →→→→→ (moving right, incrementing column)
Row 1: ←←←←← (moving left, decrementing column)
Row 2: →→→→→ (moving right again)
Staircase Stacking:
- Each row is offset vertically by:
polylineHeight + delta.latitude - Ensures the top of row N+1 is below the bottom of row N
- Creates a "staircase" effect with no vertical overlap
Boundary Validation:
- Uses ray-casting point-in-polygon algorithm
- Tests every coordinate of every polyline
- Rejects positions that exceed boundary
- Automatically moves to next row when out of space
Collision Prevention:
- Horizontal:
delta.longitudespacing between columns - Vertical:
polylineHeight + delta.latitudespacing between rows - Zigzag pattern prevents vertical alignment
- Each (row, column) position is unique
- Exponential backoff for retries
- Circuit breaker behavior for persistent failures
- Context-based cancellation propagates through all operations
- Graceful shutdown on SIGINT/SIGTERM signals
The simulator interacts with 5 REST endpoints:
- POST /operation/payload - Create order (returns 202 Accepted)
- GET /details - Get order status (polls until "Accepted")
- POST /activate - Activate order (for activate-type orders)
- POST /cancel - Cancel order (for accepted-type orders)
- POST /end - End order (cleanup for activated orders)
# Run all tests
go test ./tests/... -v
# Run tests with coverage
go test ./tests/... -cover
# Run specific test
go test ./tests/... -run TestPayloadGeneration -v- Payload generation and distribution
- GeoJSON polyline generation
- Point-in-polygon validation
- Zigzag crawl pattern logic
- Boundary constraint enforcement
- Configuration validation
- Batch processing logic
- Error handling scenarios
- Async termination channel operations
All logs are output in JSON format for easy parsing:
{
"timestamp": "2024-01-15T10:30:45Z",
"level": "INFO",
"message": "Batch completed",
"fields": {
"batchId": 3,
"successful": 20,
"failed": 0,
"duration": "2m15s"
}
}Detailed simulation results are saved to simulation_results.json:
{
"totalOrders": 200,
"successfulOrders": 198,
"failedOrders": 2,
"batchResults": [...],
"startTime": "2024-01-15T10:00:00Z",
"endTime": "2024-01-15T10:05:23Z",
"duration": 323456000000
}Generated paths are automatically exported to logs/geojsons/payloads_YYYYMMDD_HHMMSS.json for visual verification:
To Visualize:
- Navigate to https://geojson.io
- Drag and drop the generated JSON file
- View the map showing:
- Red boundary polygon with semi-transparent fill
- Blue base polyline (reference point at row 0, col 0)
- Green paths for activate-type orders
- Orange paths for accepted-type orders
What to Verify:
- ✓ All paths stay within the red boundary
- ✓ No overlapping polylines
- ✓ Zigzag pattern visible (alternating row directions)
- ✓ Staircase stacking (each row below the previous)
Click on any path to see properties: orderNumber, index, type
- Memory Usage: ~50MB for 1000 orders
- Concurrency: Supports 10+ parallel batches efficiently
- Connection Pooling: HTTP client reuses connections
- Resource Management: Proper cleanup of goroutines and channels
Issue: "activatedCount cannot exceed totalOrders"
- Ensure
activatedCount≤totalOrdersin config.yaml
Issue: Connection timeouts
- Increase
api.timeoutin configuration - Check network connectivity to API endpoint
Issue: High memory usage
- Reduce
parallelBatchescount - Decrease
totalOrdersorbatchSize
Issue: Orders stuck in pending state
- Increase
cleanup.cancelTimeout - Check API endpoint availability
Issue: "All paths exceed boundary" or insufficient paths generated
- Verify
boundary.coordinatespolygon is large enough - Check that
basePolylinestarts within the boundary (top-left corner) - Increase boundary size or decrease
deltavalues - Reduce
totalOrdersto fit within available space
Issue: Overlapping paths in GeoJSON visualization
- Increase
delta.longitudefor more horizontal spacing - Increase
delta.latitudefor more vertical row spacing - Verify generator is not shuffling payloads (check shuffle is disabled)
Issue: Invalid GeoJSON format errors
- Ensure all coordinates are in [longitude, latitude] format (not lat, lng)
- Verify longitude range: -180 to 180, latitude range: -90 to 90
- Check boundary polygon is properly closed (first point equals last point)
Rule of thumb for spacing:
delta.longitude = 0.001≈ 111 meters east-west at equatordelta.latitude = 0.001≈ 111 meters north-south- Adjust based on your polyline width and desired spacing
- Larger delta = fewer paths fit in boundary, more spacing
Tips for boundary polygons:
- Start with a rectangular boundary for simplicity
- Place
basePolylineat the top-left corner of boundary - Ensure boundary is large enough for desired
totalOrders - Use geojson.io to draw and test your boundary first
- Copy coordinates from geojson.io directly into config.yaml
Estimating capacity:
Columns per row ≈ (boundary_width / delta.longitude)
Rows available ≈ (boundary_height / (polyline_height + delta.latitude))
Max orders ≈ columns × rows
basePolyline:
coordinates: # Small 3-point route
- [-96.7994, 32.7951]
- [-96.7993, 32.7889]
- [-96.7981, 32.7803]
delta:
longitude: 0.0005 # ~55m spacing
latitude: 0.0005 # ~55m spacing
boundary:
coordinates: # ~2km × 2km area
- - [-96.81, 32.80]
- [-96.78, 32.80]
- [-96.78, 32.77]
- [-96.81, 32.77]
- [-96.81, 32.80]This configuration supports ~2,000+ non-overlapping paths.
- Follow the existing package structure
- Add tests for new functionality
- Update configuration schema if needed
- Document changes in README
- Follow Go conventions and best practices
- Use
go fmtfor formatting - Run
go vetfor static analysis - Maintain test coverage > 80%
MIT License - See LICENSE file for details
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
For issues and questions:
- Open an issue on GitHub
- Check existing documentation
- Review troubleshooting guide
- GeoJSON polyline generation with boundary constraints
- Zigzag crawl pattern for non-overlapping paths
- Point-in-polygon validation
- Async termination phase with channels
- GeoJSON export for visual verification
- Prometheus metrics export
- HTML dashboard for real-time monitoring
- Dry-run mode
- Checkpoint/resume capability
- Multiple scenario support
- Distributed mode for load testing
- Custom path generation algorithms (spiral, grid, random)
- Multi-polygon boundary support
- Real-time GeoJSON streaming