-
Notifications
You must be signed in to change notification settings - Fork 456
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First pass at MarsToxic: estimated latency but unbounded bandwidth
- Loading branch information
Showing
2 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package toxics | ||
|
||
import ( | ||
"math" | ||
"time" | ||
) | ||
|
||
// The MarsToxic simulates the communication delay to Mars based on current orbital positions. | ||
// There are more accurate orbital models, but this is a simple approximation. | ||
// | ||
// Further possibilities here: | ||
// * drop packets entirely during solar conjunction | ||
// * corrupt frames in the liminal period before/after conjunction | ||
// | ||
// We could to the hard block but we're kind of at the wrong layer to do corruption. | ||
type MarsToxic struct { | ||
// Optional additional latency in milliseconds | ||
ExtraLatency int64 `json:"extra_latency"` | ||
// Reference time for testing, if zero current time is used | ||
ReferenceTime time.Time `json:"-"` | ||
} | ||
|
||
// Since we're buffering for several minutes, we need a large buffer. | ||
// Maybe this should really be unbounded... this is actually a kind of awkward thing to model without | ||
// a, you know, hundred million kilometre long buffer of functionally infinite | ||
// capacity connecting the two points. | ||
func (t *MarsToxic) GetBufferSize() int { | ||
return 1024 * 1024 | ||
} | ||
|
||
// This is accurate to within a something like 1-2%, and probably not | ||
// hundreds/thousands of years away from 2000. | ||
// This is a simple sinusoidal approximation; a real calculation would require | ||
// quite a lot more doing (which could be fun, but...) | ||
func (t *MarsToxic) Delay() time.Duration { | ||
// Constants for Mars distance calculation | ||
minDistance := 54.6e6 // km at opposition | ||
maxDistance := 401.0e6 // km at conjunction | ||
meanDistance := (maxDistance + minDistance) / 2 | ||
amplitude := (maxDistance - minDistance) / 2 | ||
synodicPeriod := 779.96 // More precise synodic period in days | ||
|
||
// Calculate days since Jan 1, 2000 | ||
// July 27, 2018 was a recent opposition, which was day 6763 since Jan 1, 2000 | ||
baseDate := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) | ||
var daysSince2000 float64 | ||
if !t.ReferenceTime.IsZero() { | ||
daysSince2000 = t.ReferenceTime.Sub(baseDate).Hours() / 24 | ||
} else { | ||
daysSince2000 = time.Since(baseDate).Hours() / 24 | ||
} | ||
|
||
// Calculate phase based on synodic period | ||
phase := 2 * math.Pi * math.Mod(daysSince2000-6763, synodicPeriod) / synodicPeriod | ||
|
||
// Calculate current distance in kilometers | ||
distanceKm := meanDistance - amplitude*math.Cos(phase) | ||
|
||
// Speed of light is exactly 299,792.458 km/s | ||
speedOfLight := 299792.458 // km/s | ||
|
||
// One-way time = distance / speed of light | ||
// Convert to milliseconds | ||
delayMs := int64((distanceKm / speedOfLight) * 1000) | ||
|
||
// Add any extra latency specified | ||
delayMs += t.ExtraLatency | ||
|
||
return time.Duration(delayMs) * time.Millisecond | ||
} | ||
|
||
func (t *MarsToxic) Pipe(stub *ToxicStub) { | ||
for { | ||
select { | ||
case <-stub.Interrupt: | ||
return | ||
case c := <-stub.Input: | ||
if c == nil { | ||
stub.Close() | ||
return | ||
} | ||
sleep := t.Delay() - time.Since(c.Timestamp) | ||
select { | ||
case <-time.After(sleep): | ||
c.Timestamp = c.Timestamp.Add(sleep) | ||
stub.Output <- c | ||
case <-stub.Interrupt: | ||
// Exit fast without applying latency. | ||
stub.Output <- c // Don't drop any data on the floor | ||
return | ||
} | ||
} | ||
} | ||
} | ||
|
||
func init() { | ||
Register("mars", new(MarsToxic)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package toxics_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/Shopify/toxiproxy/v2/toxics" | ||
) | ||
|
||
func TestMarsDelayCalculation(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
date time.Time | ||
expected time.Duration | ||
}{ | ||
{ | ||
name: "At Opposition (Closest)", | ||
date: time.Date(2018, 7, 27, 0, 0, 0, 0, time.UTC), | ||
expected: 182 * time.Second, // ~3 minutes at closest approach | ||
}, | ||
{ | ||
name: "At Conjunction (Farthest)", | ||
date: time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC), // ~400 days after opposition | ||
expected: 1337 * time.Second, // ~22.3 minutes at farthest point | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
marsToxic := &toxics.MarsToxic{ | ||
ReferenceTime: tt.date, | ||
} | ||
|
||
delay := marsToxic.Delay() | ||
tolerance := time.Duration(float64(tt.expected) * 0.04) // 4% tolerance | ||
if diff := delay - tt.expected; diff < -tolerance || diff > tolerance { | ||
t.Errorf("Expected delay of %v (±%v), got %v (%.1f%% difference)", | ||
tt.expected, | ||
tolerance, | ||
delay, | ||
float64(diff) / float64(tt.expected) * 100, | ||
) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestMarsExtraLatencyCalculation(t *testing.T) { | ||
marsToxic := &toxics.MarsToxic{ | ||
ReferenceTime: time.Date(2018, 7, 27, 0, 0, 0, 0, time.UTC), | ||
ExtraLatency: 60000, // Add 1 minute | ||
} | ||
|
||
expected := 242 * time.Second // ~4 minutes (3 min base + 1 min extra) | ||
delay := marsToxic.Delay() | ||
|
||
tolerance := time.Duration(float64(expected) * 0.03) // 3% tolerance | ||
if diff := delay - expected; diff < -tolerance || diff > tolerance { | ||
t.Errorf("Expected delay of %v (±%v), got %v (%.1f%% difference)", | ||
expected, | ||
tolerance, | ||
delay, | ||
float64(diff) / float64(expected) * 100, | ||
) | ||
} | ||
} |