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