github.com/m3db/m3@v1.5.0/src/x/process/process_linux_test.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package process
    22  
    23  import (
    24  	"fmt"
    25  	"io/ioutil"
    26  	"math"
    27  	"os"
    28  	"path/filepath"
    29  	"testing"
    30  
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  type cleanupFn func()
    35  
    36  // Stdin
    37  // stdout
    38  // stderr
    39  // /proc/<PID>/fd
    40  // One more (not sure what it is, probably something related to the Go test runner.)
    41  const numStdProcessFiles = 5
    42  
    43  // Sometimes the number of F.Ds is higher than expected (likely due to the test runner
    44  // or other other tests that didn't clean up FDs properly.)
    45  const allowedMarginOfError = 2
    46  
    47  func TestNumFDs(t *testing.T) {
    48  	for i := 0; i <= 8; i++ {
    49  		var numFiles int
    50  		if i == 0 {
    51  			numFiles = 0
    52  		} else {
    53  			numFiles = int(math.Pow(float64(2), float64(i)))
    54  		}
    55  
    56  		func() {
    57  			numExpectedFds := numFiles + numStdProcessFiles
    58  			cleanupFn := createTempFiles(numFiles)
    59  			defer cleanupFn()
    60  
    61  			selfPID := os.Getpid()
    62  
    63  			t.Run(fmt.Sprintf("func: %s, numFiles: %d", "numFDsSlow", numFiles), func(t *testing.T) {
    64  				numFDs, err := numFDsSlow(selfPID)
    65  				require.NoError(t, err)
    66  				verifyNumFDsWithinMarginOfError(t, numExpectedFds, numFDs)
    67  			})
    68  
    69  			t.Run(fmt.Sprintf("func: %s, numFiles: %d", "NumFDs", numFiles), func(t *testing.T) {
    70  				numFDs, err := NumFDs(selfPID)
    71  				require.NoError(t, err)
    72  				verifyNumFDsWithinMarginOfError(t, numExpectedFds, numFDs)
    73  			})
    74  
    75  			t.Run(fmt.Sprintf("func: %s, numFiles: %d", "NumFDsWithDefaultBatchSleep", numFiles), func(t *testing.T) {
    76  				numFDs, err := NumFDsWithDefaultBatchSleep(selfPID)
    77  				require.NoError(t, err)
    78  				verifyNumFDsWithinMarginOfError(t, numExpectedFds, numFDs)
    79  			})
    80  		}()
    81  	}
    82  }
    83  
    84  func verifyNumFDsWithinMarginOfError(t *testing.T, expected, actual int) {
    85  	require.True(
    86  		t,
    87  		actual-expected <= allowedMarginOfError,
    88  		fmt.Sprintf("expected: %d, actual: %d, allowed margin of error: %d",
    89  			expected, actual, allowedMarginOfError),
    90  	)
    91  }
    92  
    93  func BenchmarkNumFDs(b *testing.B) {
    94  	var (
    95  		// Low for C.I and local testing, bump this up to a much larger number
    96  		// when performing actual benchmarking.
    97  		numFiles = 16000
    98  		// +5 to account for standard F.Ds that each process gets.
    99  		numExpectedFds = numFiles + numStdProcessFiles
   100  	)
   101  	cleanupFn := createTempFiles(numFiles)
   102  	defer cleanupFn()
   103  
   104  	selfPID := os.Getpid()
   105  	b.Run("numFDsSlow", func(b *testing.B) {
   106  		for i := 0; i < b.N; i++ {
   107  			numFDs, err := numFDsSlow(selfPID)
   108  			if err != nil {
   109  				b.Fatal(err)
   110  			}
   111  			if numFDs != numExpectedFds {
   112  				b.Fatalf("expected %d files but got %d", numExpectedFds, numFDs)
   113  			}
   114  		}
   115  	})
   116  
   117  	b.Run("NumFDs", func(b *testing.B) {
   118  		for i := 0; i < b.N; i++ {
   119  			numFDs, err := NumFDs(selfPID)
   120  			if err != nil {
   121  				b.Fatal(err)
   122  			}
   123  			if numFDs != numExpectedFds {
   124  				b.Fatalf("expected %d files but got %d", numExpectedFds, numFDs)
   125  			}
   126  		}
   127  	})
   128  
   129  	b.Run("NumFDsWithDefaultBatchSleep", func(b *testing.B) {
   130  		for i := 0; i < b.N; i++ {
   131  			numFDs, err := NumFDsWithDefaultBatchSleep(selfPID)
   132  			if err != nil {
   133  				b.Fatal(err)
   134  			}
   135  			if numFDs != numExpectedFds {
   136  				b.Fatalf("expected %d files but got %d", numExpectedFds, numFDs)
   137  			}
   138  		}
   139  	})
   140  }
   141  
   142  func createTempFiles(numFiles int) cleanupFn {
   143  	tempDir, err := ioutil.TempDir("", "test")
   144  	if err != nil {
   145  		panic(err)
   146  	}
   147  
   148  	files := make([]*os.File, 0, numFiles)
   149  	for i := 0; i < numFiles; i++ {
   150  		tempFilePath := filepath.Join(tempDir, fmt.Sprintf("%d.txt", i))
   151  		f, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
   152  		if err != nil {
   153  			panic(err)
   154  		}
   155  		files = append(files, f)
   156  	}
   157  
   158  	return func() {
   159  		for _, f := range files {
   160  			f.Close()
   161  		}
   162  		os.RemoveAll(tempDir)
   163  	}
   164  
   165  }