github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/stats/api_test.go (about) 1 // Copyright (c) 2015-2021, NVIDIA CORPORATION. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package stats 5 6 import ( 7 "fmt" 8 "net" 9 "strconv" 10 "strings" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/swiftstack/ProxyFS/conf" 16 "github.com/swiftstack/ProxyFS/transitions" 17 ) 18 19 type testGlobalsStruct struct { 20 sync.Mutex // Protects access to statsLog 21 t *testing.T 22 useUDP bool // Logically useTCP == !useUDP 23 udpLAddr *net.UDPAddr 24 tcpLAddr *net.TCPAddr 25 udpConn *net.UDPConn 26 tcpListener *net.TCPListener 27 statsLog []string 28 donePending bool // true if parent has called Close() to terminate testStatsd 29 doneChan chan bool // sufficient to buffer the lone true 30 doneErr error 31 stopPending bool 32 } 33 34 var testGlobals testGlobalsStruct 35 36 func TestStatsAPIviaUDP(t *testing.T) { 37 var ( 38 confMap conf.ConfMap 39 confStrings []string 40 err error 41 portString string 42 ) 43 44 testGlobals.t = t 45 46 testGlobals.useUDP = true 47 48 testGlobals.udpLAddr, err = net.ResolveUDPAddr("udp", "localhost:0") 49 if nil != err { 50 t.Fatalf("net.RessolveUDPAddr(\"udp\", \"localhost:0\") returned error: %v", err) 51 } 52 53 testGlobals.udpConn, err = net.ListenUDP("udp", testGlobals.udpLAddr) 54 if nil != err { 55 t.Fatalf("net.ListenUDP(\"udp\", testGlobals.udpLAddr) returned error: %v", err) 56 } 57 58 _, portString, err = net.SplitHostPort(testGlobals.udpConn.LocalAddr().String()) 59 if nil != err { 60 t.Fatalf("net.SplitHostPort(testGlobals.udpConn.LocalAddr().String()) returned error: %v", err) 61 } 62 63 testGlobals.statsLog = make([]string, 0, 100) 64 65 testGlobals.doneChan = make(chan bool, 1) 66 67 testGlobals.doneErr = nil 68 testGlobals.stopPending = false 69 70 go testStatsd() 71 72 confStrings = []string{ 73 "Logging.LogFilePath=/dev/null", 74 "Cluster.WhoAmI=nobody", 75 "FSGlobals.VolumeGroupList=", 76 "FSGlobals.CheckpointHeaderConsensusAttempts=5", 77 "FSGlobals.MountRetryLimit=6", 78 "FSGlobals.MountRetryDelay=1s", 79 "FSGlobals.MountRetryExpBackoff=2", 80 "FSGlobals.LogCheckpointHeaderPosts=true", 81 "FSGlobals.TryLockBackoffMin=10ms", 82 "FSGlobals.TryLockBackoffMax=50ms", 83 "FSGlobals.TryLockSerializationThreshhold=5", 84 "FSGlobals.SymlinkMax=32", 85 "FSGlobals.CoalesceElementChunkSize=16", 86 "Stats.IPAddr=localhost", 87 "Stats.UDPPort=" + portString, 88 "Stats.BufferLength=1000", 89 "Stats.MaxLatency=100ms", 90 } 91 92 confMap, err = conf.MakeConfMapFromStrings(confStrings) 93 if nil != err { 94 t.Fatalf("conf.MakeConfMapFromStrings(confStrings) returned error: %v", err) 95 } 96 97 err = transitions.Up(confMap) 98 if nil != err { 99 t.Fatalf("transitions.Up(confMap) returned error: %v", err) 100 } 101 102 testSendStats() 103 testVerifyStats() 104 105 err = transitions.Down(confMap) 106 if nil != err { 107 t.Fatalf("transitions.Down() returned error: %v", err) 108 } 109 110 testGlobals.stopPending = true 111 112 err = testGlobals.udpConn.Close() 113 if nil != err { 114 t.Fatalf("testGlobals.udpConn.Close() returned error: %v", err) 115 } 116 117 _ = <-testGlobals.doneChan 118 119 if nil != testGlobals.doneErr { 120 t.Fatalf("testStatsd() returned error: %v", testGlobals.doneErr) 121 } 122 } 123 124 func TestStatsAPIviaTCP(t *testing.T) { 125 var ( 126 confMap conf.ConfMap 127 confStrings []string 128 err error 129 portString string 130 ) 131 132 testGlobals.t = t 133 134 testGlobals.useUDP = false 135 136 testGlobals.tcpLAddr, err = net.ResolveTCPAddr("tcp", "localhost:0") 137 if nil != err { 138 t.Fatalf("net.RessolveTCPAddr(\"tcp\", \"localhost:0\") returned error: %v", err) 139 } 140 141 testGlobals.tcpListener, err = net.ListenTCP("tcp", testGlobals.tcpLAddr) 142 if nil != err { 143 t.Fatalf("net.ListenTCP(\"tcp\", testGlobals.tcpLAddr) returned error: %v", err) 144 } 145 146 _, portString, err = net.SplitHostPort(testGlobals.tcpListener.Addr().String()) 147 if nil != err { 148 t.Fatalf("net.SplitHostPort(testGlobals.tcpListener.Addr().String()) returned error: %v", err) 149 } 150 151 testGlobals.statsLog = make([]string, 0, 100) 152 153 testGlobals.doneChan = make(chan bool, 1) 154 155 testGlobals.doneErr = nil 156 testGlobals.stopPending = false 157 158 go testStatsd() 159 160 confStrings = []string{ 161 "Logging.LogFilePath=/dev/null", 162 "Cluster.WhoAmI=nobody", 163 "FSGlobals.VolumeGroupList=", 164 "FSGlobals.CheckpointHeaderConsensusAttempts=5", 165 "FSGlobals.MountRetryLimit=6", 166 "FSGlobals.MountRetryDelay=1s", 167 "FSGlobals.MountRetryExpBackoff=2", 168 "FSGlobals.LogCheckpointHeaderPosts=true", 169 "FSGlobals.TryLockBackoffMin=10ms", 170 "FSGlobals.TryLockBackoffMax=50ms", 171 "FSGlobals.TryLockSerializationThreshhold=5", 172 "FSGlobals.SymlinkMax=32", 173 "Stats.IPAddr=localhost", 174 "Stats.TCPPort=" + portString, 175 "Stats.BufferLength=1000", 176 "Stats.MaxLatency=100ms", 177 } 178 179 confMap, err = conf.MakeConfMapFromStrings(confStrings) 180 if nil != err { 181 t.Fatalf("conf.MakeConfMapFromStrings(confStrings) returned error: %v", err) 182 } 183 184 err = transitions.Up(confMap) 185 if nil != err { 186 t.Fatalf("transitions.Up() returned error: %v", err) 187 } 188 189 testSendStats() 190 testVerifyStats() 191 192 err = transitions.Down(confMap) 193 if nil != err { 194 t.Fatalf("transitions.Down() returned error: %v", err) 195 } 196 197 testGlobals.stopPending = true 198 199 err = testGlobals.tcpListener.Close() 200 if nil != err { 201 t.Fatalf("testGlobals.tcpListener.Close() returned error: %v", err) 202 } 203 204 _ = <-testGlobals.doneChan 205 206 if nil != testGlobals.doneErr { 207 t.Fatalf("testStatsd() returned error: %v", testGlobals.doneErr) 208 } 209 } 210 211 func testStatsd() { 212 var ( 213 buf []byte 214 bufConsumed int 215 err error 216 testTCPConn *net.TCPConn 217 ) 218 219 buf = make([]byte, 2048) 220 221 for { 222 if testGlobals.useUDP { 223 bufConsumed, _, err = testGlobals.udpConn.ReadFromUDP(buf) 224 if nil != err { 225 if !testGlobals.stopPending { 226 testGlobals.doneErr = err 227 } 228 testGlobals.doneChan <- true 229 return 230 } 231 232 if 0 == bufConsumed { 233 if !testGlobals.stopPending { 234 err = fmt.Errorf("0 == bufConsumed") 235 testGlobals.doneErr = err 236 } 237 testGlobals.doneChan <- true 238 return 239 } 240 241 testGlobals.Lock() 242 testGlobals.statsLog = append(testGlobals.statsLog, string(buf[:bufConsumed])) 243 testGlobals.Unlock() 244 } else { // testGlobals.useTCP 245 testTCPConn, err = testGlobals.tcpListener.AcceptTCP() 246 if nil != err { 247 if !testGlobals.stopPending { 248 testGlobals.doneErr = err 249 } 250 testGlobals.doneChan <- true 251 return 252 } 253 if nil == testTCPConn { 254 if !testGlobals.stopPending { 255 err = fmt.Errorf("nil == testTCPConn") 256 testGlobals.doneErr = err 257 } 258 testGlobals.doneChan <- true 259 return 260 } 261 262 bufConsumed, err = testTCPConn.Read(buf) 263 if nil != err { 264 if !testGlobals.stopPending { 265 testGlobals.doneErr = err 266 } 267 testGlobals.doneChan <- true 268 return 269 } 270 if 0 == bufConsumed { 271 if !testGlobals.stopPending { 272 err = fmt.Errorf("0 == bufConsumed") 273 testGlobals.doneErr = err 274 } 275 testGlobals.doneChan <- true 276 return 277 } 278 279 testGlobals.Lock() 280 testGlobals.statsLog = append(testGlobals.statsLog, string(buf[:bufConsumed])) 281 testGlobals.Unlock() 282 283 err = testTCPConn.Close() 284 if nil != err { 285 if !testGlobals.stopPending { 286 testGlobals.doneErr = err 287 } 288 testGlobals.doneChan <- true 289 return 290 } 291 } 292 } 293 } 294 295 func testSendStats() { 296 var ( 297 sleepDuration time.Duration 298 ) 299 300 sleepDuration = 4 * globals.maxLatency 301 302 IncrementOperations(&LogSegCreateOps) 303 IncrementOperationsBy(&FileFlushOps, 3) 304 IncrementOperationsAndBytes(SwiftObjTail, 1024) 305 IncrementOperationsEntriesAndBytes(DirRead, 48, 2048) 306 IncrementOperationsAndBucketedBytes(FileRead, 4096) 307 IncrementOperationsBucketedBytesAndAppendedOverwritten(FileWrite, 8192, 0, 0) 308 time.Sleep(sleepDuration) // ensures above doesn't get merged with below 309 IncrementOperationsBucketedBytesAndAppendedOverwritten(FileWrite, 16384, 400, 0) 310 time.Sleep(sleepDuration) // ensures above doesn't get merged with below 311 IncrementOperationsBucketedBytesAndAppendedOverwritten(FileWrite, 32768, 0, 500) 312 time.Sleep(sleepDuration) // ensures above doesn't get merged with below 313 IncrementOperationsBucketedBytesAndAppendedOverwritten(FileWrite, 65536, 600, 700) 314 IncrementOperationsBucketedEntriesAndBucketedBytes(FileReadplan, 7, 131072) 315 time.Sleep(sleepDuration) // ensures we get all of the stats entries 316 } 317 318 func testVerifyStats() { 319 var ( 320 expectedStatValue uint64 321 expectedStats []string 322 expectedStatsValueMap map[string]uint64 323 ok bool 324 statCount uint16 325 statName string 326 statLine string 327 statLineSplitOnColon []string 328 statLineSuffixSplitOnBar []string 329 statValue uint64 330 statValueIncrement uint64 331 statsCountMap map[string]uint16 332 statsDumpMap map[string]uint64 333 ) 334 335 // Build a slice of the stats we expect 336 337 expectedStats = []string{ 338 LogSegCreateOps + ":1|c", 339 340 FileFlushOps + ":3|c", 341 342 SwiftObjTailOps + ":1|c", 343 SwiftObjTailBytes + ":1024|c", 344 345 DirReadOps + ":1|c", 346 DirReadEntries + ":48|c", 347 DirReadBytes + ":2048|c", 348 349 FileReadOps + ":1|c", 350 FileReadOps4K + ":1|c", 351 FileReadBytes + ":4096|c", 352 353 FileWriteOps + ":1|c", 354 FileWriteOps8K + ":1|c", 355 FileWriteBytes + ":8192|c", 356 357 FileWriteOps + ":1|c", 358 FileWriteOps16K + ":1|c", 359 FileWriteBytes + ":16384|c", 360 FileWriteAppended + ":400|c", 361 362 FileWriteOps + ":1|c", 363 FileWriteOps32K + ":1|c", 364 FileWriteBytes + ":32768|c", 365 FileWriteOverwritten + ":500|c", 366 367 FileWriteOps + ":1|c", 368 FileWriteOps64K + ":1|c", 369 FileWriteBytes + ":65536|c", 370 FileWriteAppended + ":600|c", 371 FileWriteOverwritten + ":700|c", 372 373 FileReadplanOps + ":1|c", 374 FileReadplanOpsOver64K + ":1|c", 375 FileReadplanBytes + ":131072|c", 376 FileReadplanOpsEntriesTo16 + ":1|c", 377 } 378 379 // Check that the stats sent to the TCP/UDP port are what we expect 380 // 381 // Note that this test has been written so that it does not depend on stats 382 // appearing in the same order they are sent in testSendStats(). 383 384 statsCountMap = make(map[string]uint16) 385 386 testGlobals.Lock() 387 388 if len(testGlobals.statsLog) != len(expectedStats) { 389 testGlobals.t.Fatalf("verifyStats() failed... wrong number of statsLog elements") 390 } 391 392 for _, statLine = range testGlobals.statsLog { 393 statCount, ok = statsCountMap[statLine] 394 if ok { 395 statCount++ 396 } else { 397 statCount = 1 398 } 399 statsCountMap[statLine] = statCount 400 } 401 402 testGlobals.Unlock() 403 404 for _, statLine = range expectedStats { 405 statCount, ok = statsCountMap[statLine] 406 if ok { 407 statCount-- 408 if 0 == statCount { 409 delete(statsCountMap, statLine) 410 } else { 411 statsCountMap[statLine] = statCount 412 } 413 } else { 414 testGlobals.t.Fatalf("verifyStats() failed... missing stat: %v", statLine) 415 } 416 } 417 418 if 0 < len(statsCountMap) { 419 for statLine = range statsCountMap { 420 testGlobals.t.Logf("verifyStats() failed... extra stat: %v", statLine) 421 } 422 testGlobals.t.FailNow() 423 } 424 425 // Compress expectedStats to be comparable to Dump() return 426 427 expectedStatsValueMap = make(map[string]uint64) 428 429 for _, statLine = range expectedStats { 430 statLineSplitOnColon = strings.Split(statLine, ":") 431 432 statName = statLineSplitOnColon[0] 433 434 statLineSuffixSplitOnBar = strings.Split(statLineSplitOnColon[1], "|") 435 436 statValueIncrement, _ = strconv.ParseUint(statLineSuffixSplitOnBar[0], 10, 64) 437 438 statValue, ok = expectedStatsValueMap[statName] 439 if ok { 440 statValue += statValueIncrement 441 } else { 442 statValue = statValueIncrement 443 } 444 expectedStatsValueMap[statName] = statValue 445 } 446 447 // Check that the stats held in memory are what we expect 448 449 statsDumpMap = Dump() 450 451 if len(statsDumpMap) != len(expectedStatsValueMap) { 452 testGlobals.t.Fatalf("verifyStats() failed... wrong number of statsDumpMap elements") 453 } 454 455 for statName, statValue = range statsDumpMap { 456 expectedStatValue, ok = expectedStatsValueMap[statName] 457 if !ok { 458 testGlobals.t.Fatalf("verifyStats() failed... received unpected statName: %v", statName) 459 } 460 if statValue != expectedStatValue { 461 testGlobals.t.Fatalf("verifyStats() failed... received unexpected statValue (%v) for statName %v (should have been %v)", statValue, statName, expectedStatValue) 462 } 463 } 464 }