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  }