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

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