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