github.com/nozzle/golangci-lint@v1.49.0-nz3/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=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", "--deadline=30m", "--disable-all", "--enable=govet"} 80 } 81 82 func runGolangciLintForBench(b *testing.B) { 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.B), 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.B) 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 }