@@ -2,11 +2,13 @@ package commands
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
6
7
"go/build"
7
8
"go/token"
8
9
"log"
9
10
"os"
11
+ "runtime"
10
12
"strings"
11
13
"time"
12
14
@@ -21,6 +23,7 @@ import (
21
23
"github.com/golangci/golangci-lint/pkg/result/processors"
22
24
"github.com/sirupsen/logrus"
23
25
"github.com/spf13/cobra"
26
+ "github.com/spf13/pflag"
24
27
"github.com/spf13/viper"
25
28
"golang.org/x/tools/go/loader"
26
29
)
@@ -51,6 +54,8 @@ func (e *Executor) initRun() {
51
54
runCmd .Flags ().StringSliceVar (& rc .BuildTags , "build-tags" , []string {}, "Build tags (not all linters support them)" )
52
55
runCmd .Flags ().DurationVar (& rc .Deadline , "deadline" , time .Minute , "Deadline for total work" )
53
56
runCmd .Flags ().BoolVar (& rc .AnalyzeTests , "tests" , false , "Analyze tests (*_test.go)" )
57
+ runCmd .Flags ().BoolVar (& rc .PrintResourcesUsage , "print-resources-usage" , false , "Print avg and max memory usage of golangci-lint and total time" )
58
+ runCmd .Flags ().StringVarP (& rc .Config , "config" , "c" , "" , "Read config from file path `PATH`" )
54
59
55
60
// Linters settings config
56
61
lsc := & e .cfg .LintersSettings
@@ -98,8 +103,6 @@ func (e *Executor) initRun() {
98
103
runCmd .Flags ().StringVar (& ic .DiffFromRevision , "new-from-rev" , "" , "Show only new issues created after git revision `REV`" )
99
104
runCmd .Flags ().StringVar (& ic .DiffPatchFilePath , "new-from-patch" , "" , "Show only new issues created in git patch with file path `PATH`" )
100
105
101
- runCmd .Flags ().StringVarP (& e .cfg .Run .Config , "config" , "c" , "" , "Read config from file path `PATH`" )
102
-
103
106
e .parseConfig (runCmd )
104
107
}
105
108
@@ -240,45 +243,53 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (chan result.
240
243
return runner .Run (ctx , linters , lintCtx ), nil
241
244
}
242
245
243
- func (e * Executor ) executeRun (cmd * cobra.Command , args []string ) {
244
- ctx , cancel := context .WithTimeout (context .Background (), e .cfg .Run .Deadline )
245
- defer cancel ()
246
+ func (e * Executor ) runAndPrint (ctx context.Context , args []string ) error {
247
+ issues , err := e .runAnalysis (ctx , args )
248
+ if err != nil {
249
+ return err
250
+ }
246
251
247
- defer func (startedAt time.Time ) {
248
- logrus .Infof ("Run took %s" , time .Since (startedAt ))
249
- }(time .Now ())
252
+ var p printers.Printer
253
+ if e .cfg .Output .Format == config .OutFormatJSON {
254
+ p = printers .NewJSON ()
255
+ } else {
256
+ p = printers .NewText (e .cfg .Output .PrintIssuedLine ,
257
+ e .cfg .Output .Format == config .OutFormatColoredLineNumber , e .cfg .Output .PrintLinterName )
258
+ }
259
+ gotAnyIssues , err := p .Print (issues )
260
+ if err != nil {
261
+ return fmt .Errorf ("can't print %d issues: %s" , len (issues ), err )
262
+ }
250
263
251
- if e .cfg .Output .PrintWelcomeMessage {
252
- fmt .Println ("Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)" )
264
+ if gotAnyIssues {
265
+ e .exitCode = e .cfg .Run .ExitCodeIfIssuesFound
266
+ return nil
253
267
}
254
268
255
- f := func () error {
256
- issues , err := e .runAnalysis (ctx , args )
257
- if err != nil {
258
- return err
259
- }
269
+ return nil
270
+ }
260
271
261
- var p printers.Printer
262
- if e .cfg .Output .Format == config .OutFormatJSON {
263
- p = printers .NewJSON ()
264
- } else {
265
- p = printers .NewText (e .cfg .Output .PrintIssuedLine ,
266
- e .cfg .Output .Format == config .OutFormatColoredLineNumber , e .cfg .Output .PrintLinterName )
267
- }
268
- gotAnyIssues , err := p .Print (issues )
269
- if err != nil {
270
- return fmt .Errorf ("can't print %d issues: %s" , len (issues ), err )
272
+ func (e * Executor ) executeRun (cmd * cobra.Command , args []string ) {
273
+ needTrackResources := e .cfg .Run .IsVerbose || e .cfg .Run .PrintResourcesUsage
274
+ trackResourcesEndCh := make (chan struct {})
275
+ defer func () { // XXX: this defer must be before ctx.cancel defer
276
+ if needTrackResources { // wait until resource tracking finished to print properly
277
+ <- trackResourcesEndCh
271
278
}
279
+ }()
272
280
273
- if gotAnyIssues {
274
- e .exitCode = e .cfg .Run .ExitCodeIfIssuesFound
275
- return nil
276
- }
281
+ ctx , cancel := context .WithTimeout (context .Background (), e .cfg .Run .Deadline )
282
+ defer cancel ()
277
283
278
- return nil
284
+ if needTrackResources {
285
+ go watchResources (ctx , trackResourcesEndCh )
286
+ }
287
+
288
+ if e .cfg .Output .PrintWelcomeMessage {
289
+ fmt .Println ("Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)" )
279
290
}
280
291
281
- if err := f ( ); err != nil {
292
+ if err := e . runAndPrint ( ctx , args ); err != nil {
282
293
log .Print (err )
283
294
if e .exitCode == 0 {
284
295
e .exitCode = exitCodeIfFailure
@@ -289,7 +300,10 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
289
300
func (e * Executor ) parseConfig (cmd * cobra.Command ) {
290
301
// XXX: hack with double parsing to acces "config" option here
291
302
if err := cmd .ParseFlags (os .Args ); err != nil {
292
- log .Fatalf ("Can't parse agrs: %s" , err )
303
+ if err == pflag .ErrHelp {
304
+ return
305
+ }
306
+ log .Fatalf ("Can't parse args: %s" , err )
293
307
}
294
308
295
309
if err := viper .BindPFlags (cmd .Flags ()); err != nil {
@@ -318,4 +332,63 @@ func (e *Executor) parseConfig(cmd *cobra.Command) {
318
332
if err := viper .Unmarshal (& e .cfg ); err != nil {
319
333
log .Fatalf ("Can't unmarshal config by viper: %s" , err )
320
334
}
335
+
336
+ if err := e .validateConfig (); err != nil {
337
+ log .Fatal (err )
338
+ }
339
+ }
340
+
341
+ func (e * Executor ) validateConfig () error {
342
+ c := e .cfg
343
+ if len (c .Run .Args ) != 0 {
344
+ return errors .New ("option run.args in config aren't supported now" )
345
+ }
346
+
347
+ if c .Run .CPUProfilePath != "" {
348
+ return errors .New ("option run.cpuprofilepath in config isn't allowed" )
349
+ }
350
+
351
+ return nil
352
+ }
353
+
354
+ func watchResources (ctx context.Context , done chan struct {}) {
355
+ startedAt := time .Now ()
356
+
357
+ rssValues := []uint64 {}
358
+ ticker := time .NewTicker (100 * time .Millisecond )
359
+ defer ticker .Stop ()
360
+
361
+ for {
362
+ var m runtime.MemStats
363
+ runtime .ReadMemStats (& m )
364
+
365
+ rssValues = append (rssValues , m .Sys )
366
+
367
+ stop := false
368
+ select {
369
+ case <- ctx .Done ():
370
+ stop = true
371
+ case <- ticker .C : // track every second
372
+ }
373
+
374
+ if stop {
375
+ break
376
+ }
377
+ }
378
+
379
+ var avg , max uint64
380
+ for _ , v := range rssValues {
381
+ avg += v
382
+ if v > max {
383
+ max = v
384
+ }
385
+ }
386
+ avg /= uint64 (len (rssValues ))
387
+
388
+ const MB = 1024 * 1024
389
+ maxMB := float64 (max ) / MB
390
+ logrus .Infof ("Memory: %d samples, avg is %.1fMB, max is %.1fMB" ,
391
+ len (rssValues ), float64 (avg )/ MB , maxMB )
392
+ logrus .Infof ("Execution took %s" , time .Since (startedAt ))
393
+ close (done )
321
394
}
0 commit comments