github.com/nalekseevs/itns-golangci-lint@v1.0.2/test/bench/bench_test.go (about)

     1  package bench
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"go/build"
     8  	"log"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	gops "github.com/mitchellh/go-ps"
    18  	"github.com/shirou/gopsutil/v3/process"
    19  
    20  	"github.com/nalekseevs/itns-golangci-lint/test/testshared"
    21  )
    22  
    23  func chdir(b testing.TB, dir string) {
    24  	if err := os.Chdir(dir); err != nil {
    25  		b.Fatalf("can't chdir to %s: %s", dir, err)
    26  	}
    27  }
    28  
    29  func prepareGoSource(b testing.TB) {
    30  	chdir(b, filepath.Join(build.Default.GOROOT, "src"))
    31  }
    32  
    33  func prepareGithubProject(owner, name string) func(testing.TB) {
    34  	return func(b testing.TB) {
    35  		dir := filepath.Join(build.Default.GOPATH, "src", "github.com", owner, name)
    36  		_, err := os.Stat(dir)
    37  		if os.IsNotExist(err) {
    38  			repo := fmt.Sprintf("https://github.com/%s/%s.git", owner, name)
    39  			err = exec.Command("git", "clone", repo, dir).Run()
    40  			if err != nil {
    41  				b.Fatalf("can't git clone %s/%s: %s", owner, name, err)
    42  			}
    43  		}
    44  		chdir(b, dir)
    45  	}
    46  }
    47  
    48  func getBenchLintersArgsNoMegacheck() []string {
    49  	return []string{
    50  		"--enable=gocyclo",
    51  		"--enable=errcheck",
    52  		"--enable=dupl",
    53  		"--enable=ineffassign",
    54  		"--enable=unconvert",
    55  		"--enable=goconst",
    56  		"--enable=gosec",
    57  	}
    58  }
    59  
    60  func getBenchLintersArgs() []string {
    61  	return append([]string{
    62  		"--enable=megacheck",
    63  	}, getBenchLintersArgsNoMegacheck()...)
    64  }
    65  
    66  func printCommand(cmd string, args ...string) {
    67  	if os.Getenv("PRINT_CMD") != "1" {
    68  		return
    69  	}
    70  	var quotedArgs []string
    71  	for _, a := range args {
    72  		quotedArgs = append(quotedArgs, strconv.Quote(a))
    73  	}
    74  
    75  	log.Printf("%s %s", cmd, strings.Join(quotedArgs, " "))
    76  }
    77  
    78  func getGolangciLintCommonArgs() []string {
    79  	return []string{"run", "--no-config", "--issues-exit-code=0", "--timeout=30m", "--disable-all", "--enable=govet"}
    80  }
    81  
    82  func runGolangciLintForBench(b testing.TB) {
    83  	args := getGolangciLintCommonArgs()
    84  	args = append(args, getBenchLintersArgs()...)
    85  	printCommand("golangci-lint", args...)
    86  	out, err := exec.Command("golangci-lint", args...).CombinedOutput()
    87  	if err != nil {
    88  		b.Fatalf("can't run golangci-lint: %s, %s", err, out)
    89  	}
    90  }
    91  
    92  func getGoLinesTotalCount(b *testing.B) int {
    93  	cmd := exec.Command("bash", "-c", `find . -name "*.go" | fgrep -v vendor | xargs wc -l | tail -1`)
    94  	out, err := cmd.CombinedOutput()
    95  	if err != nil {
    96  		b.Fatalf("can't run go lines counter: %s", err)
    97  	}
    98  
    99  	parts := bytes.Split(bytes.TrimSpace(out), []byte(" "))
   100  	n, err := strconv.Atoi(string(parts[0]))
   101  	if err != nil {
   102  		b.Fatalf("can't parse go lines count: %s", err)
   103  	}
   104  
   105  	return n
   106  }
   107  
   108  func getLinterMemoryMB(b *testing.B, progName string) (int, error) {
   109  	processes, err := gops.Processes()
   110  	if err != nil {
   111  		b.Fatalf("Can't get processes: %s", err)
   112  	}
   113  
   114  	var progPID int
   115  	for _, p := range processes {
   116  		if p.Executable() == progName {
   117  			progPID = p.Pid()
   118  			break
   119  		}
   120  	}
   121  	if progPID == 0 {
   122  		return 0, errors.New("no process")
   123  	}
   124  
   125  	allProgPIDs := []int{progPID}
   126  	for _, p := range processes {
   127  		if p.PPid() == progPID {
   128  			allProgPIDs = append(allProgPIDs, p.Pid())
   129  		}
   130  	}
   131  
   132  	var totalProgMemBytes uint64
   133  	for _, pid := range allProgPIDs {
   134  		p, err := process.NewProcess(int32(pid))
   135  		if err != nil {
   136  			continue // subprocess could die
   137  		}
   138  
   139  		mi, err := p.MemoryInfo()
   140  		if err != nil {
   141  			continue
   142  		}
   143  
   144  		totalProgMemBytes += mi.RSS
   145  	}
   146  
   147  	return int(totalProgMemBytes / 1024 / 1024), nil
   148  }
   149  
   150  func trackPeakMemoryUsage(b *testing.B, doneCh <-chan struct{}, progName string) chan int {
   151  	resCh := make(chan int)
   152  	go func() {
   153  		var peakUsedMemMB int
   154  		t := time.NewTicker(time.Millisecond * 5)
   155  		defer t.Stop()
   156  
   157  		for {
   158  			select {
   159  			case <-doneCh:
   160  				resCh <- peakUsedMemMB
   161  				close(resCh)
   162  				return
   163  			case <-t.C:
   164  			}
   165  
   166  			m, err := getLinterMemoryMB(b, progName)
   167  			if err != nil {
   168  				continue
   169  			}
   170  			if m > peakUsedMemMB {
   171  				peakUsedMemMB = m
   172  			}
   173  		}
   174  	}()
   175  	return resCh
   176  }
   177  
   178  type runResult struct {
   179  	peakMemMB int
   180  	duration  time.Duration
   181  }
   182  
   183  func runOne(b *testing.B, run func(testing.TB), progName string) *runResult {
   184  	doneCh := make(chan struct{})
   185  	peakMemCh := trackPeakMemoryUsage(b, doneCh, progName)
   186  	startedAt := time.Now()
   187  	run(b)
   188  	duration := time.Since(startedAt)
   189  	close(doneCh)
   190  
   191  	peakUsedMemMB := <-peakMemCh
   192  	return &runResult{
   193  		peakMemMB: peakUsedMemMB,
   194  		duration:  duration,
   195  	}
   196  }
   197  
   198  func BenchmarkGolangciLint(b *testing.B) {
   199  	testshared.InstallGolangciLint(b)
   200  
   201  	type bcase struct {
   202  		name    string
   203  		prepare func(testing.TB)
   204  	}
   205  	bcases := []bcase{
   206  		{
   207  			name:    "self repo",
   208  			prepare: prepareGithubProject("golangci", "golangci-lint"),
   209  		},
   210  		{
   211  			name:    "hugo",
   212  			prepare: prepareGithubProject("gohugoio", "hugo"),
   213  		},
   214  		{
   215  			name:    "go-ethereum",
   216  			prepare: prepareGithubProject("ethereum", "go-ethereum"),
   217  		},
   218  		{
   219  			name:    "beego",
   220  			prepare: prepareGithubProject("astaxie", "beego"),
   221  		},
   222  		{
   223  			name:    "terraform",
   224  			prepare: prepareGithubProject("hashicorp", "terraform"),
   225  		},
   226  		{
   227  			name:    "consul",
   228  			prepare: prepareGithubProject("hashicorp", "consul"),
   229  		},
   230  		{
   231  			name:    "go source code",
   232  			prepare: prepareGoSource,
   233  		},
   234  	}
   235  	for _, bc := range bcases {
   236  		bc.prepare(b)
   237  		lc := getGoLinesTotalCount(b)
   238  		result := runOne(b, runGolangciLintForBench, "golangci-lint")
   239  
   240  		log.Printf("%s (%d kLoC): time: %s, memory: %dMB",
   241  			bc.name, lc/1000,
   242  			result.duration,
   243  			result.peakMemMB,
   244  		)
   245  	}
   246  }