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