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 }