1package coordinate 2 3import ( 4 "math" 5 "testing" 6 "time" 7) 8 9func TestPerformance_Line(t *testing.T) { 10 const spacing = 10 * time.Millisecond 11 const nodes, cycles = 10, 1000 12 config := DefaultConfig() 13 clients, err := GenerateClients(nodes, config) 14 if err != nil { 15 t.Fatal(err) 16 } 17 truth := GenerateLine(nodes, spacing) 18 Simulate(clients, truth, cycles) 19 stats := Evaluate(clients, truth) 20 if stats.ErrorAvg > 0.0018 || stats.ErrorMax > 0.0092 { 21 t.Fatalf("performance stats are out of spec: %v", stats) 22 } 23} 24 25func TestPerformance_Grid(t *testing.T) { 26 const spacing = 10 * time.Millisecond 27 const nodes, cycles = 25, 1000 28 config := DefaultConfig() 29 clients, err := GenerateClients(nodes, config) 30 if err != nil { 31 t.Fatal(err) 32 } 33 truth := GenerateGrid(nodes, spacing) 34 Simulate(clients, truth, cycles) 35 stats := Evaluate(clients, truth) 36 if stats.ErrorAvg > 0.0015 || stats.ErrorMax > 0.022 { 37 t.Fatalf("performance stats are out of spec: %v", stats) 38 } 39} 40 41func TestPerformance_Split(t *testing.T) { 42 const lan, wan = 1 * time.Millisecond, 10 * time.Millisecond 43 const nodes, cycles = 25, 1000 44 config := DefaultConfig() 45 clients, err := GenerateClients(nodes, config) 46 if err != nil { 47 t.Fatal(err) 48 } 49 truth := GenerateSplit(nodes, lan, wan) 50 Simulate(clients, truth, cycles) 51 stats := Evaluate(clients, truth) 52 if stats.ErrorAvg > 0.000060 || stats.ErrorMax > 0.00048 { 53 t.Fatalf("performance stats are out of spec: %v", stats) 54 } 55} 56 57func TestPerformance_Height(t *testing.T) { 58 const radius = 100 * time.Millisecond 59 const nodes, cycles = 25, 1000 60 61 // Constrain us to two dimensions so that we can just exactly represent 62 // the circle. 63 config := DefaultConfig() 64 config.Dimensionality = 2 65 clients, err := GenerateClients(nodes, config) 66 if err != nil { 67 t.Fatal(err) 68 } 69 70 // Generate truth where the first coordinate is in the "middle" because 71 // it's equidistant from all the nodes, but it will have an extra radius 72 // added to the distance, so it should come out above all the others. 73 truth := GenerateCircle(nodes, radius) 74 Simulate(clients, truth, cycles) 75 76 // Make sure the height looks reasonable with the regular nodes all in a 77 // plane, and the center node up above. 78 for i, _ := range clients { 79 coord := clients[i].GetCoordinate() 80 if i == 0 { 81 if coord.Height < 0.97*radius.Seconds() { 82 t.Fatalf("height is out of spec: %9.6f", coord.Height) 83 } 84 } else { 85 if coord.Height > 0.03*radius.Seconds() { 86 t.Fatalf("height is out of spec: %9.6f", coord.Height) 87 } 88 } 89 } 90 stats := Evaluate(clients, truth) 91 if stats.ErrorAvg > 0.0025 || stats.ErrorMax > 0.064 { 92 t.Fatalf("performance stats are out of spec: %v", stats) 93 } 94} 95 96func TestPerformance_Drift(t *testing.T) { 97 const dist = 500 * time.Millisecond 98 const nodes = 4 99 config := DefaultConfig() 100 config.Dimensionality = 2 101 clients, err := GenerateClients(nodes, config) 102 if err != nil { 103 t.Fatal(err) 104 } 105 106 // Do some icky surgery on the clients to put them into a square, up in 107 // the first quadrant. 108 clients[0].coord.Vec = []float64{0.0, 0.0} 109 clients[1].coord.Vec = []float64{0.0, dist.Seconds()} 110 clients[2].coord.Vec = []float64{dist.Seconds(), dist.Seconds()} 111 clients[3].coord.Vec = []float64{dist.Seconds(), dist.Seconds()} 112 113 // Make a corresponding truth matrix. The nodes are laid out like this 114 // so the distances are all equal, except for the diagonal: 115 // 116 // (1) <- dist -> (2) 117 // 118 // | <- dist | 119 // | | 120 // | dist -> | 121 // 122 // (0) <- dist -> (3) 123 // 124 truth := make([][]time.Duration, nodes) 125 for i := range truth { 126 truth[i] = make([]time.Duration, nodes) 127 } 128 for i := 0; i < nodes; i++ { 129 for j := i + 1; j < nodes; j++ { 130 rtt := dist 131 if (i%2 == 0) && (j%2 == 0) { 132 rtt = time.Duration(math.Sqrt2 * float64(rtt)) 133 } 134 truth[i][j], truth[j][i] = rtt, rtt 135 } 136 } 137 138 calcCenterError := func() float64 { 139 min, max := clients[0].GetCoordinate(), clients[0].GetCoordinate() 140 for i := 1; i < nodes; i++ { 141 coord := clients[i].GetCoordinate() 142 for j, v := range coord.Vec { 143 min.Vec[j] = math.Min(min.Vec[j], v) 144 max.Vec[j] = math.Max(max.Vec[j], v) 145 } 146 } 147 148 mid := make([]float64, config.Dimensionality) 149 for i, _ := range mid { 150 mid[i] = min.Vec[i] + (max.Vec[i]-min.Vec[i])/2 151 } 152 return magnitude(mid) 153 } 154 155 // Let the simulation run for a while to stabilize, then snap a baseline 156 // for the center error. 157 Simulate(clients, truth, 1000) 158 baseline := calcCenterError() 159 160 // Now run for a bunch more cycles and see if gravity pulls the center 161 // in the right direction. 162 Simulate(clients, truth, 10000) 163 if error := calcCenterError(); error > 0.8*baseline { 164 t.Fatalf("drift performance out of spec: %9.6f -> %9.6f", baseline, error) 165 } 166} 167 168func TestPerformance_Random(t *testing.T) { 169 const mean, deviation = 100 * time.Millisecond, 10 * time.Millisecond 170 const nodes, cycles = 25, 1000 171 config := DefaultConfig() 172 clients, err := GenerateClients(nodes, config) 173 if err != nil { 174 t.Fatal(err) 175 } 176 truth := GenerateRandom(nodes, mean, deviation) 177 Simulate(clients, truth, cycles) 178 stats := Evaluate(clients, truth) 179 if stats.ErrorAvg > 0.075 || stats.ErrorMax > 0.33 { 180 t.Fatalf("performance stats are out of spec: %v", stats) 181 } 182} 183