gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/xds/internal/xdsclient/load/store_test.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package load 19 20 import ( 21 "fmt" 22 "sort" 23 "sync" 24 "testing" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/go-cmp/cmp/cmpopts" 28 ) 29 30 var ( 31 dropCategories = []string{"drop_for_real", "drop_for_fun"} 32 localities = []string{"locality-A", "locality-B"} 33 errTest = fmt.Errorf("test error") 34 ) 35 36 // rpcData wraps the rpc counts and load data to be pushed to the store. 37 type rpcData struct { 38 start, success, failure int 39 serverData map[string]float64 // Will be reported with successful RPCs. 40 } 41 42 // TestDrops spawns a bunch of goroutines which report drop data. After the 43 // goroutines have exited, the test dumps the stats from the Store and makes 44 // sure they are as expected. 45 func TestDrops(t *testing.T) { 46 var ( 47 drops = map[string]int{ 48 dropCategories[0]: 30, 49 dropCategories[1]: 40, 50 "": 10, 51 } 52 wantStoreData = &Data{ 53 TotalDrops: 80, 54 Drops: map[string]uint64{ 55 dropCategories[0]: 30, 56 dropCategories[1]: 40, 57 }, 58 } 59 ) 60 61 ls := perClusterStore{} 62 var wg sync.WaitGroup 63 for category, count := range drops { 64 for i := 0; i < count; i++ { 65 wg.Add(1) 66 go func(c string) { 67 ls.CallDropped(c) 68 wg.Done() 69 }(category) 70 } 71 } 72 wg.Wait() 73 74 gotStoreData := ls.stats() 75 if diff := cmp.Diff(wantStoreData, gotStoreData, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval")); diff != "" { 76 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 77 } 78 } 79 80 // TestLocalityStats spawns a bunch of goroutines which report rpc and load 81 // data. After the goroutines have exited, the test dumps the stats from the 82 // Store and makes sure they are as expected. 83 func TestLocalityStats(t *testing.T) { 84 var ( 85 localityData = map[string]rpcData{ 86 localities[0]: { 87 start: 40, 88 success: 20, 89 failure: 10, 90 serverData: map[string]float64{"net": 1, "disk": 2, "cpu": 3, "mem": 4}, 91 }, 92 localities[1]: { 93 start: 80, 94 success: 40, 95 failure: 20, 96 serverData: map[string]float64{"net": 1, "disk": 2, "cpu": 3, "mem": 4}, 97 }, 98 } 99 wantStoreData = &Data{ 100 LocalityStats: map[string]LocalityData{ 101 localities[0]: { 102 RequestStats: RequestData{Succeeded: 20, Errored: 10, InProgress: 10}, 103 LoadStats: map[string]ServerLoadData{ 104 "net": {Count: 20, Sum: 20}, 105 "disk": {Count: 20, Sum: 40}, 106 "cpu": {Count: 20, Sum: 60}, 107 "mem": {Count: 20, Sum: 80}, 108 }, 109 }, 110 localities[1]: { 111 RequestStats: RequestData{Succeeded: 40, Errored: 20, InProgress: 20}, 112 LoadStats: map[string]ServerLoadData{ 113 "net": {Count: 40, Sum: 40}, 114 "disk": {Count: 40, Sum: 80}, 115 "cpu": {Count: 40, Sum: 120}, 116 "mem": {Count: 40, Sum: 160}, 117 }, 118 }, 119 }, 120 } 121 ) 122 123 ls := perClusterStore{} 124 var wg sync.WaitGroup 125 for locality, data := range localityData { 126 wg.Add(data.start) 127 for i := 0; i < data.start; i++ { 128 go func(l string) { 129 ls.CallStarted(l) 130 wg.Done() 131 }(locality) 132 } 133 // The calls to callStarted() need to happen before the other calls are 134 // made. Hence the wait here. 135 wg.Wait() 136 137 wg.Add(data.success) 138 for i := 0; i < data.success; i++ { 139 go func(l string, serverData map[string]float64) { 140 ls.CallFinished(l, nil) 141 for n, d := range serverData { 142 ls.CallServerLoad(l, n, d) 143 } 144 wg.Done() 145 }(locality, data.serverData) 146 } 147 wg.Add(data.failure) 148 for i := 0; i < data.failure; i++ { 149 go func(l string) { 150 ls.CallFinished(l, errTest) 151 wg.Done() 152 }(locality) 153 } 154 wg.Wait() 155 } 156 157 gotStoreData := ls.stats() 158 if diff := cmp.Diff(wantStoreData, gotStoreData, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval")); diff != "" { 159 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 160 } 161 } 162 163 func TestResetAfterStats(t *testing.T) { 164 // Push a bunch of drops, call stats and load stats, and leave inProgress to be non-zero. 165 // Dump the stats. Verify expexted 166 // Push the same set of loads as before 167 // Now dump and verify the newly expected ones. 168 var ( 169 drops = map[string]int{ 170 dropCategories[0]: 30, 171 dropCategories[1]: 40, 172 } 173 localityData = map[string]rpcData{ 174 localities[0]: { 175 start: 40, 176 success: 20, 177 failure: 10, 178 serverData: map[string]float64{"net": 1, "disk": 2, "cpu": 3, "mem": 4}, 179 }, 180 localities[1]: { 181 start: 80, 182 success: 40, 183 failure: 20, 184 serverData: map[string]float64{"net": 1, "disk": 2, "cpu": 3, "mem": 4}, 185 }, 186 } 187 wantStoreData = &Data{ 188 TotalDrops: 70, 189 Drops: map[string]uint64{ 190 dropCategories[0]: 30, 191 dropCategories[1]: 40, 192 }, 193 LocalityStats: map[string]LocalityData{ 194 localities[0]: { 195 RequestStats: RequestData{Succeeded: 20, Errored: 10, InProgress: 10}, 196 LoadStats: map[string]ServerLoadData{ 197 "net": {Count: 20, Sum: 20}, 198 "disk": {Count: 20, Sum: 40}, 199 "cpu": {Count: 20, Sum: 60}, 200 "mem": {Count: 20, Sum: 80}, 201 }, 202 }, 203 localities[1]: { 204 RequestStats: RequestData{Succeeded: 40, Errored: 20, InProgress: 20}, 205 LoadStats: map[string]ServerLoadData{ 206 "net": {Count: 40, Sum: 40}, 207 "disk": {Count: 40, Sum: 80}, 208 "cpu": {Count: 40, Sum: 120}, 209 "mem": {Count: 40, Sum: 160}, 210 }, 211 }, 212 }, 213 } 214 ) 215 216 reportLoad := func(ls *perClusterStore) { 217 for category, count := range drops { 218 for i := 0; i < count; i++ { 219 ls.CallDropped(category) 220 } 221 } 222 for locality, data := range localityData { 223 for i := 0; i < data.start; i++ { 224 ls.CallStarted(locality) 225 } 226 for i := 0; i < data.success; i++ { 227 ls.CallFinished(locality, nil) 228 for n, d := range data.serverData { 229 ls.CallServerLoad(locality, n, d) 230 } 231 } 232 for i := 0; i < data.failure; i++ { 233 ls.CallFinished(locality, errTest) 234 } 235 } 236 } 237 238 ls := perClusterStore{} 239 reportLoad(&ls) 240 gotStoreData := ls.stats() 241 if diff := cmp.Diff(wantStoreData, gotStoreData, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval")); diff != "" { 242 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 243 } 244 245 // The above call to stats() should have reset all load reports except the 246 // inProgress rpc count. We are now going to push the same load data into 247 // the store. So, we should expect to see twice the count for inProgress. 248 for _, l := range localities { 249 ls := wantStoreData.LocalityStats[l] 250 ls.RequestStats.InProgress *= 2 251 wantStoreData.LocalityStats[l] = ls 252 } 253 reportLoad(&ls) 254 gotStoreData = ls.stats() 255 if diff := cmp.Diff(wantStoreData, gotStoreData, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval")); diff != "" { 256 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 257 } 258 } 259 260 var sortDataSlice = cmp.Transformer("SortDataSlice", func(in []*Data) []*Data { 261 out := append([]*Data(nil), in...) // Copy input to avoid mutating it 262 sort.Slice(out, 263 func(i, j int) bool { 264 if out[i].Cluster < out[j].Cluster { 265 return true 266 } 267 if out[i].Cluster == out[j].Cluster { 268 return out[i].Service < out[j].Service 269 } 270 return false 271 }, 272 ) 273 return out 274 }) 275 276 // Test all load are returned for the given clusters, and all clusters are 277 // reported if no cluster is specified. 278 func TestStoreStats(t *testing.T) { 279 var ( 280 testClusters = []string{"c0", "c1", "c2"} 281 testServices = []string{"s0", "s1"} 282 testLocality = "test-locality" 283 ) 284 285 store := NewStore() 286 for _, c := range testClusters { 287 for _, s := range testServices { 288 store.PerCluster(c, s).CallStarted(testLocality) 289 store.PerCluster(c, s).CallServerLoad(testLocality, "abc", 123) 290 store.PerCluster(c, s).CallDropped("dropped") 291 store.PerCluster(c, s).CallFinished(testLocality, nil) 292 } 293 } 294 295 wantC0 := []*Data{ 296 { 297 Cluster: "c0", Service: "s0", 298 TotalDrops: 1, Drops: map[string]uint64{"dropped": 1}, 299 LocalityStats: map[string]LocalityData{ 300 "test-locality": { 301 RequestStats: RequestData{Succeeded: 1}, 302 LoadStats: map[string]ServerLoadData{"abc": {Count: 1, Sum: 123}}, 303 }, 304 }, 305 }, 306 { 307 Cluster: "c0", Service: "s1", 308 TotalDrops: 1, Drops: map[string]uint64{"dropped": 1}, 309 LocalityStats: map[string]LocalityData{ 310 "test-locality": { 311 RequestStats: RequestData{Succeeded: 1}, 312 LoadStats: map[string]ServerLoadData{"abc": {Count: 1, Sum: 123}}, 313 }, 314 }, 315 }, 316 } 317 // Call Stats with just "c0", this should return data for "c0", and not 318 // touch data for other clusters. 319 gotC0 := store.Stats([]string{"c0"}) 320 if diff := cmp.Diff(wantC0, gotC0, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval"), sortDataSlice); diff != "" { 321 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 322 } 323 324 wantOther := []*Data{ 325 { 326 Cluster: "c1", Service: "s0", 327 TotalDrops: 1, Drops: map[string]uint64{"dropped": 1}, 328 LocalityStats: map[string]LocalityData{ 329 "test-locality": { 330 RequestStats: RequestData{Succeeded: 1}, 331 LoadStats: map[string]ServerLoadData{"abc": {Count: 1, Sum: 123}}, 332 }, 333 }, 334 }, 335 { 336 Cluster: "c1", Service: "s1", 337 TotalDrops: 1, Drops: map[string]uint64{"dropped": 1}, 338 LocalityStats: map[string]LocalityData{ 339 "test-locality": { 340 RequestStats: RequestData{Succeeded: 1}, 341 LoadStats: map[string]ServerLoadData{"abc": {Count: 1, Sum: 123}}, 342 }, 343 }, 344 }, 345 { 346 Cluster: "c2", Service: "s0", 347 TotalDrops: 1, Drops: map[string]uint64{"dropped": 1}, 348 LocalityStats: map[string]LocalityData{ 349 "test-locality": { 350 RequestStats: RequestData{Succeeded: 1}, 351 LoadStats: map[string]ServerLoadData{"abc": {Count: 1, Sum: 123}}, 352 }, 353 }, 354 }, 355 { 356 Cluster: "c2", Service: "s1", 357 TotalDrops: 1, Drops: map[string]uint64{"dropped": 1}, 358 LocalityStats: map[string]LocalityData{ 359 "test-locality": { 360 RequestStats: RequestData{Succeeded: 1}, 361 LoadStats: map[string]ServerLoadData{"abc": {Count: 1, Sum: 123}}, 362 }, 363 }, 364 }, 365 } 366 // Call Stats with empty slice, this should return data for all the 367 // remaining clusters, and not include c0 (because c0 data was cleared). 368 gotOther := store.Stats(nil) 369 if diff := cmp.Diff(wantOther, gotOther, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval"), sortDataSlice); diff != "" { 370 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 371 } 372 } 373 374 // Test the cases that if a cluster doesn't have load to report, its data is not 375 // appended to the slice returned by Stats(). 376 func TestStoreStatsEmptyDataNotReported(t *testing.T) { 377 var ( 378 testServices = []string{"s0", "s1"} 379 testLocality = "test-locality" 380 ) 381 382 store := NewStore() 383 // "c0"'s RPCs all finish with success. 384 for _, s := range testServices { 385 store.PerCluster("c0", s).CallStarted(testLocality) 386 store.PerCluster("c0", s).CallFinished(testLocality, nil) 387 } 388 // "c1"'s RPCs never finish (always inprocess). 389 for _, s := range testServices { 390 store.PerCluster("c1", s).CallStarted(testLocality) 391 } 392 393 want0 := []*Data{ 394 { 395 Cluster: "c0", Service: "s0", 396 LocalityStats: map[string]LocalityData{ 397 "test-locality": {RequestStats: RequestData{Succeeded: 1}}, 398 }, 399 }, 400 { 401 Cluster: "c0", Service: "s1", 402 LocalityStats: map[string]LocalityData{ 403 "test-locality": {RequestStats: RequestData{Succeeded: 1}}, 404 }, 405 }, 406 { 407 Cluster: "c1", Service: "s0", 408 LocalityStats: map[string]LocalityData{ 409 "test-locality": {RequestStats: RequestData{InProgress: 1}}, 410 }, 411 }, 412 { 413 Cluster: "c1", Service: "s1", 414 LocalityStats: map[string]LocalityData{ 415 "test-locality": {RequestStats: RequestData{InProgress: 1}}, 416 }, 417 }, 418 } 419 // Call Stats with empty slice, this should return data for all the 420 // clusters. 421 got0 := store.Stats(nil) 422 if diff := cmp.Diff(want0, got0, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval"), sortDataSlice); diff != "" { 423 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 424 } 425 426 want1 := []*Data{ 427 { 428 Cluster: "c1", Service: "s0", 429 LocalityStats: map[string]LocalityData{ 430 "test-locality": {RequestStats: RequestData{InProgress: 1}}, 431 }, 432 }, 433 { 434 Cluster: "c1", Service: "s1", 435 LocalityStats: map[string]LocalityData{ 436 "test-locality": {RequestStats: RequestData{InProgress: 1}}, 437 }, 438 }, 439 } 440 // Call Stats with empty slice again, this should return data only for "c1", 441 // because "c0" data was cleared, but "c1" has in-progress RPCs. 442 got1 := store.Stats(nil) 443 if diff := cmp.Diff(want1, got1, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Data{}, "ReportInterval"), sortDataSlice); diff != "" { 444 t.Errorf("store.stats() returned unexpected diff (-want +got):\n%s", diff) 445 } 446 }