You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
6.0 KiB
219 lines
6.0 KiB
package simulation |
|
|
|
import ( |
|
"math/rand" |
|
) |
|
|
|
type ThirdPartyServiceLogic struct{} |
|
|
|
type ServiceStatus struct { |
|
IsUp bool |
|
LastCheck int |
|
FailureCount int |
|
SuccessCount int |
|
RateLimitHits int |
|
} |
|
|
|
func (t ThirdPartyServiceLogic) Tick(props map[string]any, queue []*Request, tick int) ([]*Request, bool) { |
|
// Extract third-party service properties |
|
provider := AsString(props["provider"]) |
|
if provider == "" { |
|
provider = "Generic" // default provider |
|
} |
|
|
|
baseLatency := int(AsFloat64(props["latency"])) |
|
if baseLatency == 0 { |
|
baseLatency = 200 // default 200ms latency |
|
} |
|
|
|
// Get service status from props (persistent state) |
|
status, ok := props["_serviceStatus"].(ServiceStatus) |
|
if !ok { |
|
status = ServiceStatus{ |
|
IsUp: true, |
|
LastCheck: tick, |
|
FailureCount: 0, |
|
SuccessCount: 0, |
|
RateLimitHits: 0, |
|
} |
|
} |
|
|
|
currentTime := tick * 100 // Convert tick to milliseconds |
|
|
|
// Simulate service availability and characteristics based on provider |
|
reliability := t.getProviderReliability(provider) |
|
rateLimitRPS := t.getProviderRateLimit(provider) |
|
latencyVariance := t.getProviderLatencyVariance(provider) |
|
|
|
// Check if service is down and should recover |
|
if !status.IsUp { |
|
// Services typically recover after some time |
|
if currentTime-status.LastCheck > 30000 { // 30 seconds downtime |
|
status.IsUp = true |
|
status.FailureCount = 0 |
|
} |
|
} |
|
|
|
// Apply rate limiting - third-party services often have strict limits |
|
requestsThisTick := len(queue) |
|
if requestsThisTick > rateLimitRPS { |
|
status.RateLimitHits++ |
|
// Only process up to rate limit |
|
queue = queue[:rateLimitRPS] |
|
} |
|
|
|
output := []*Request{} |
|
|
|
for _, req := range queue { |
|
reqCopy := *req |
|
|
|
// Simulate service availability |
|
if !status.IsUp { |
|
// Service is down - simulate timeout/error |
|
reqCopy.LatencyMS += 10000 // 10 second timeout |
|
reqCopy.Path = append(reqCopy.Path, "third-party-timeout") |
|
status.FailureCount++ |
|
} else { |
|
// Service is up - calculate response time |
|
serviceLatency := t.calculateServiceLatency(provider, baseLatency, latencyVariance) |
|
|
|
// Random failure based on reliability |
|
if rand.Float64() > reliability { |
|
// Service call failed |
|
serviceLatency += 5000 // 5 second timeout on failure |
|
reqCopy.Path = append(reqCopy.Path, "third-party-failed") |
|
status.FailureCount++ |
|
|
|
// If too many failures, mark service as down |
|
if status.FailureCount > 5 { |
|
status.IsUp = false |
|
status.LastCheck = currentTime |
|
} |
|
} else { |
|
// Successful service call |
|
reqCopy.Path = append(reqCopy.Path, "third-party-success") |
|
status.SuccessCount++ |
|
|
|
// Reset failure count on successful calls |
|
if status.FailureCount > 0 { |
|
status.FailureCount-- |
|
} |
|
} |
|
|
|
reqCopy.LatencyMS += serviceLatency |
|
} |
|
|
|
output = append(output, &reqCopy) |
|
} |
|
|
|
// Update persistent state |
|
props["_serviceStatus"] = status |
|
|
|
// Health check: service is healthy if external service is up and not excessively rate limited |
|
// Allow some rate limiting but not too much |
|
maxRateLimitHits := 10 // Allow up to 10 rate limit hits before considering unhealthy |
|
healthy := status.IsUp && status.RateLimitHits < maxRateLimitHits |
|
|
|
return output, healthy |
|
} |
|
|
|
// getProviderReliability returns the reliability percentage for different providers |
|
func (t ThirdPartyServiceLogic) getProviderReliability(provider string) float64 { |
|
switch provider { |
|
case "Stripe": |
|
return 0.999 // 99.9% uptime |
|
case "Twilio": |
|
return 0.998 // 99.8% uptime |
|
case "SendGrid": |
|
return 0.997 // 99.7% uptime |
|
case "AWS": |
|
return 0.9995 // 99.95% uptime |
|
case "Google": |
|
return 0.9999 // 99.99% uptime |
|
case "Slack": |
|
return 0.995 // 99.5% uptime |
|
case "GitHub": |
|
return 0.996 // 99.6% uptime |
|
case "Shopify": |
|
return 0.998 // 99.8% uptime |
|
default: |
|
return 0.99 // 99% uptime for generic services |
|
} |
|
} |
|
|
|
// getProviderRateLimit returns the rate limit (requests per tick) for different providers |
|
func (t ThirdPartyServiceLogic) getProviderRateLimit(provider string) int { |
|
switch provider { |
|
case "Stripe": |
|
return 100 // 100 requests per second (per tick in our sim) |
|
case "Twilio": |
|
return 50 // More restrictive |
|
case "SendGrid": |
|
return 200 // Email is typically higher volume |
|
case "AWS": |
|
return 1000 // Very high limits |
|
case "Google": |
|
return 500 // High but controlled |
|
case "Slack": |
|
return 30 // Very restrictive for chat APIs |
|
case "GitHub": |
|
return 60 // GitHub API limits |
|
case "Shopify": |
|
return 80 // E-commerce API limits |
|
default: |
|
return 100 // Default rate limit |
|
} |
|
} |
|
|
|
// getProviderLatencyVariance returns the latency variance factor for different providers |
|
func (t ThirdPartyServiceLogic) getProviderLatencyVariance(provider string) float64 { |
|
switch provider { |
|
case "Stripe": |
|
return 0.3 // Low variance, consistent performance |
|
case "Twilio": |
|
return 0.5 // Moderate variance |
|
case "SendGrid": |
|
return 0.4 // Email services are fairly consistent |
|
case "AWS": |
|
return 0.2 // Very consistent |
|
case "Google": |
|
return 0.25 // Very consistent |
|
case "Slack": |
|
return 0.6 // Chat services can be variable |
|
case "GitHub": |
|
return 0.4 // Moderate variance |
|
case "Shopify": |
|
return 0.5 // E-commerce can be variable under load |
|
default: |
|
return 0.5 // Default variance |
|
} |
|
} |
|
|
|
// calculateServiceLatency computes the actual latency including variance |
|
func (t ThirdPartyServiceLogic) calculateServiceLatency(provider string, baseLatency int, variance float64) int { |
|
// Add random variance to base latency |
|
varianceMs := float64(baseLatency) * variance |
|
randomVariance := (rand.Float64() - 0.5) * 2 * varianceMs // -variance to +variance |
|
|
|
finalLatency := float64(baseLatency) + randomVariance |
|
|
|
// Ensure minimum latency (can't be negative or too low) |
|
if finalLatency < 10 { |
|
finalLatency = 10 |
|
} |
|
|
|
// Add provider-specific baseline adjustments |
|
switch provider { |
|
case "AWS", "Google": |
|
// Cloud providers are typically fast |
|
finalLatency *= 0.8 |
|
case "Slack": |
|
// Chat APIs can be slower |
|
finalLatency *= 1.2 |
|
case "Twilio": |
|
// Telecom APIs have processing overhead |
|
finalLatency *= 1.1 |
|
} |
|
|
|
return int(finalLatency) |
|
}
|
|
|