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  }