2018-05-05 09:24:37 +03:00
package commands
import (
"context"
2018-05-12 10:13:37 +03:00
"errors"
2018-05-05 09:24:37 +03:00
"fmt"
2018-05-06 22:58:04 +03:00
"go/build"
2018-05-08 17:13:16 +03:00
"go/token"
2018-05-26 19:58:17 +03:00
"io/ioutil"
2018-05-05 09:24:37 +03:00
"log"
2018-05-11 22:01:57 +03:00
"os"
2018-05-12 10:13:37 +03:00
"runtime"
2018-05-05 09:24:37 +03:00
"strings"
2018-05-06 19:08:34 +03:00
"time"
2018-05-05 09:24:37 +03:00
2018-05-07 09:48:43 +03:00
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil"
2018-05-05 19:43:18 +03:00
"github.com/golangci/golangci-lint/pkg"
2018-05-05 09:24:37 +03:00
"github.com/golangci/golangci-lint/pkg/config"
2018-05-06 22:58:04 +03:00
"github.com/golangci/golangci-lint/pkg/fsutils"
"github.com/golangci/golangci-lint/pkg/golinters"
2018-05-08 11:54:30 +03:00
"github.com/golangci/golangci-lint/pkg/printers"
2018-05-05 09:24:37 +03:00
"github.com/golangci/golangci-lint/pkg/result"
2018-05-05 19:43:18 +03:00
"github.com/golangci/golangci-lint/pkg/result/processors"
2018-05-07 21:44:40 +03:00
"github.com/sirupsen/logrus"
2018-05-05 09:24:37 +03:00
"github.com/spf13/cobra"
2018-05-12 10:13:37 +03:00
"github.com/spf13/pflag"
2018-05-11 22:01:57 +03:00
"github.com/spf13/viper"
2018-05-06 22:58:04 +03:00
"golang.org/x/tools/go/loader"
2018-05-05 09:24:37 +03:00
)
2018-05-06 19:08:34 +03:00
const exitCodeIfFailure = 3
2018-05-05 09:24:37 +03:00
func ( e * Executor ) initRun ( ) {
var runCmd = & cobra . Command {
Use : "run" ,
Short : "Run linters" ,
Run : e . executeRun ,
}
e . rootCmd . AddCommand ( runCmd )
2018-05-11 22:01:57 +03:00
// Output config
oc := & e . cfg . Output
runCmd . Flags ( ) . StringVar ( & oc . Format , "out-format" ,
2018-05-05 09:24:37 +03:00
config . OutFormatColoredLineNumber ,
fmt . Sprintf ( "Format of output: %s" , strings . Join ( config . OutFormats , "|" ) ) )
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . BoolVar ( & oc . PrintIssuedLine , "print-issued-lines" , true , "Print lines of code with issue" )
runCmd . Flags ( ) . BoolVar ( & oc . PrintLinterName , "print-linter-name" , true , "Print linter name in issue line" )
2018-05-26 09:48:16 +03:00
runCmd . Flags ( ) . BoolVar ( & oc . PrintWelcomeMessage , "print-welcome" , false , "Print welcome message" )
2018-05-08 11:54:30 +03:00
2018-05-11 22:01:57 +03:00
// Run config
rc := & e . cfg . Run
2018-05-05 19:43:18 +03:00
runCmd . Flags ( ) . IntVar ( & rc . ExitCodeIfIssuesFound , "issues-exit-code" ,
2018-05-05 11:08:14 +03:00
1 , "Exit code when issues were found" )
2018-05-06 13:28:00 +03:00
runCmd . Flags ( ) . StringSliceVar ( & rc . BuildTags , "build-tags" , [ ] string { } , "Build tags (not all linters support them)" )
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . DurationVar ( & rc . Deadline , "deadline" , time . Minute , "Deadline for total work" )
runCmd . Flags ( ) . BoolVar ( & rc . AnalyzeTests , "tests" , false , "Analyze tests (*_test.go)" )
2018-05-12 10:13:37 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . PrintResourcesUsage , "print-resources-usage" , false , "Print avg and max memory usage of golangci-lint and total time" )
runCmd . Flags ( ) . StringVarP ( & rc . Config , "config" , "c" , "" , "Read config from file path `PATH`" )
2018-05-19 22:09:20 +03:00
runCmd . Flags ( ) . BoolVar ( & rc . NoConfig , "no-config" , false , "Don't read config" )
2018-05-05 19:43:18 +03:00
2018-05-11 22:01:57 +03:00
// Linters settings config
lsc := & e . cfg . LintersSettings
runCmd . Flags ( ) . BoolVar ( & lsc . Errcheck . CheckTypeAssertions , "errcheck.check-type-assertions" , false , "Errcheck: check for ignored type assertion results" )
runCmd . Flags ( ) . BoolVar ( & lsc . Errcheck . CheckAssignToBlank , "errcheck.check-blank" , false , "Errcheck: check for errors assigned to blank identifier: _ = errFunc()" )
2018-05-05 22:22:21 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . BoolVar ( & lsc . Govet . CheckShadowing , "govet.check-shadowing" , false , "Govet: check for shadowed variables" )
2018-05-06 07:20:12 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . Float64Var ( & lsc . Golint . MinConfidence , "golint.min-confidence" , 0.8 , "Golint: minimum confidence of a problem to print it" )
2018-05-06 09:41:48 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . BoolVar ( & lsc . Gofmt . Simplify , "gofmt.simplify" , true , "Gofmt: simplify code" )
2018-05-06 13:25:50 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . IntVar ( & lsc . Gocyclo . MinComplexity , "gocyclo.min-complexity" ,
2018-05-08 13:33:00 +03:00
30 , "Minimal complexity of function to report it" )
2018-05-06 15:24:45 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . BoolVar ( & lsc . Maligned . SuggestNewOrder , "maligned.suggest-new" , false , "Maligned: print suggested more optimal struct fields ordering" )
2018-05-06 20:28:59 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . IntVar ( & lsc . Dupl . Threshold , "dupl.threshold" ,
2018-05-08 12:10:24 +03:00
150 , "Dupl: Minimal threshold to detect copy-paste" )
2018-05-07 12:43:52 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . IntVar ( & lsc . Goconst . MinStringLen , "goconst.min-len" ,
2018-05-07 12:43:52 +03:00
3 , "Goconst: minimum constant string length" )
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . IntVar ( & lsc . Goconst . MinOccurrencesCount , "goconst.min-occurrences" ,
2018-05-26 21:59:09 +08:00
3 , "Goconst: minimum occurrences of constant string count to trigger issue" )
2018-05-06 22:58:04 +03:00
2018-05-11 22:01:57 +03:00
// Linters config
lc := & e . cfg . Linters
runCmd . Flags ( ) . StringSliceVarP ( & lc . Enable , "enable" , "E" , [ ] string { } , "Enable specific linter" )
runCmd . Flags ( ) . StringSliceVarP ( & lc . Disable , "disable" , "D" , [ ] string { } , "Disable specific linter" )
runCmd . Flags ( ) . BoolVar ( & lc . EnableAll , "enable-all" , false , "Enable all linters" )
runCmd . Flags ( ) . BoolVar ( & lc . DisableAll , "disable-all" , false , "Disable all linters" )
runCmd . Flags ( ) . StringSliceVarP ( & lc . Presets , "presets" , "p" , [ ] string { } ,
fmt . Sprintf ( "Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all" , strings . Join ( pkg . AllPresets ( ) , "|" ) ) )
2018-05-15 08:59:50 +03:00
runCmd . Flags ( ) . BoolVar ( & lc . Fast , "fast" , false , "Run only fast linters from enabled linters set" )
2018-05-06 19:08:34 +03:00
2018-05-11 22:01:57 +03:00
// Issues config
ic := & e . cfg . Issues
runCmd . Flags ( ) . StringSliceVarP ( & ic . ExcludePatterns , "exclude" , "e" , [ ] string { } , "Exclude issue by regexp" )
runCmd . Flags ( ) . BoolVar ( & ic . UseDefaultExcludes , "exclude-use-default" , true ,
2018-05-08 12:10:24 +03:00
fmt . Sprintf ( "Use or not use default excludes: (%s)" , strings . Join ( config . DefaultExcludePatterns , "|" ) ) )
2018-05-08 08:15:55 +03:00
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . IntVar ( & ic . MaxIssuesPerLinter , "max-issues-per-linter" , 50 , "Maximum issues count per one linter. Set to 0 to disable" )
runCmd . Flags ( ) . IntVar ( & ic . MaxSameIssues , "max-same-issues" , 3 , "Maximum count of issues with the same text. Set to 0 to disable" )
2018-05-08 09:55:38 +03:00
2018-05-15 08:59:50 +03:00
runCmd . Flags ( ) . BoolVarP ( & ic . Diff , "new" , "n" , false , "Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed" )
2018-05-11 22:01:57 +03:00
runCmd . Flags ( ) . StringVar ( & ic . DiffFromRevision , "new-from-rev" , "" , "Show only new issues created after git revision `REV`" )
runCmd . Flags ( ) . StringVar ( & ic . DiffPatchFilePath , "new-from-patch" , "" , "Show only new issues created in git patch with file path `PATH`" )
2018-05-08 17:13:16 +03:00
2018-05-11 22:01:57 +03:00
e . parseConfig ( runCmd )
2018-05-05 09:24:37 +03:00
}
2018-05-06 22:58:04 +03:00
func isFullImportNeeded ( linters [ ] pkg . Linter ) bool {
for _ , linter := range linters {
lc := pkg . GetLinterConfig ( linter . Name ( ) )
if lc . DoesFullImport {
return true
}
}
return false
}
2018-05-07 09:48:43 +03:00
func isSSAReprNeeded ( linters [ ] pkg . Linter ) bool {
for _ , linter := range linters {
lc := pkg . GetLinterConfig ( linter . Name ( ) )
if lc . NeedsSSARepr {
return true
}
}
return false
}
2018-05-06 22:58:04 +03:00
func loadWholeAppIfNeeded ( ctx context . Context , linters [ ] pkg . Linter , cfg * config . Run , paths * fsutils . ProjectPaths ) ( * loader . Program , * loader . Config , error ) {
if ! isFullImportNeeded ( linters ) {
return nil , nil , nil
}
startedAt := time . Now ( )
defer func ( ) {
2018-05-07 21:44:40 +03:00
logrus . Infof ( "Program loading took %s" , time . Since ( startedAt ) )
2018-05-06 22:58:04 +03:00
} ( )
bctx := build . Default
bctx . BuildTags = append ( bctx . BuildTags , cfg . BuildTags ... )
loadcfg := & loader . Config {
2018-05-07 09:09:10 +03:00
Build : & bctx ,
AllowErrors : true , // Try to analyze event partially
2018-05-06 22:58:04 +03:00
}
2018-05-08 12:28:36 +03:00
rest , err := loadcfg . FromArgs ( paths . MixedPaths ( ) , cfg . AnalyzeTests )
2018-05-06 22:58:04 +03:00
if err != nil {
return nil , nil , fmt . Errorf ( "can't parepare load config with paths: %s" , err )
}
if len ( rest ) > 0 {
return nil , nil , fmt . Errorf ( "unhandled loading paths: %v" , rest )
}
prog , err := loadcfg . Load ( )
if err != nil {
return nil , nil , fmt . Errorf ( "can't load paths: %s" , err )
}
return prog , loadcfg , nil
}
2018-05-07 09:48:43 +03:00
func buildSSAProgram ( ctx context . Context , lprog * loader . Program ) * ssa . Program {
startedAt := time . Now ( )
defer func ( ) {
2018-05-07 21:44:40 +03:00
logrus . Infof ( "SSA repr building took %s" , time . Since ( startedAt ) )
2018-05-07 09:48:43 +03:00
} ( )
ssaProg := ssautil . CreateProgram ( lprog , ssa . GlobalDebug )
ssaProg . Build ( )
return ssaProg
}
2018-05-06 22:58:04 +03:00
func buildLintCtx ( ctx context . Context , linters [ ] pkg . Linter , cfg * config . Config ) ( * golinters . Context , error ) {
args := cfg . Run . Args
if len ( args ) == 0 {
args = [ ] string { "./..." }
}
2018-05-08 12:28:36 +03:00
paths , err := fsutils . GetPathsForAnalysis ( ctx , args , cfg . Run . AnalyzeTests )
2018-05-06 22:58:04 +03:00
if err != nil {
return nil , err
}
prog , loaderConfig , err := loadWholeAppIfNeeded ( ctx , linters , & cfg . Run , paths )
if err != nil {
return nil , err
}
2018-05-07 09:48:43 +03:00
var ssaProg * ssa . Program
if prog != nil && isSSAReprNeeded ( linters ) {
ssaProg = buildSSAProgram ( ctx , prog )
}
2018-05-06 22:58:04 +03:00
return & golinters . Context {
Paths : paths ,
Cfg : cfg ,
Program : prog ,
2018-05-07 09:48:43 +03:00
SSAProgram : ssaProg ,
2018-05-06 22:58:04 +03:00
LoaderConfig : loaderConfig ,
} , nil
}
2018-05-19 16:18:23 +03:00
func ( e * Executor ) runAnalysis ( ctx context . Context , args [ ] string ) ( <- chan result . Issue , error ) {
2018-05-06 19:08:34 +03:00
e . cfg . Run . Args = args
2018-05-06 13:25:50 +03:00
2018-05-11 22:01:57 +03:00
linters , err := pkg . GetEnabledLinters ( e . cfg )
2018-05-06 22:58:04 +03:00
if err != nil {
return nil , err
}
lintCtx , err := buildLintCtx ( ctx , linters , e . cfg )
if err != nil {
return nil , err
}
2018-05-11 22:01:57 +03:00
excludePatterns := e . cfg . Issues . ExcludePatterns
if e . cfg . Issues . UseDefaultExcludes {
2018-05-08 12:10:24 +03:00
excludePatterns = append ( excludePatterns , config . DefaultExcludePatterns ... )
}
var excludeTotalPattern string
if len ( excludePatterns ) != 0 {
excludeTotalPattern = fmt . Sprintf ( "(%s)" , strings . Join ( excludePatterns , "|" ) )
}
2018-05-08 17:13:16 +03:00
fset := token . NewFileSet ( )
if lintCtx . Program != nil {
fset = lintCtx . Program . Fset
}
2018-05-06 19:08:34 +03:00
runner := pkg . SimpleRunner {
Processors : [ ] processors . Processor {
2018-05-13 12:05:34 +03:00
processors . NewPathPrettifier ( ) , // must be before diff processor at least
2018-05-08 12:10:24 +03:00
processors . NewExclude ( excludeTotalPattern ) ,
2018-05-08 17:13:16 +03:00
processors . NewCgo ( ) ,
processors . NewNolint ( fset ) ,
2018-05-07 21:44:40 +03:00
processors . NewUniqByLine ( ) ,
2018-05-11 22:01:57 +03:00
processors . NewDiff ( e . cfg . Issues . Diff , e . cfg . Issues . DiffFromRevision , e . cfg . Issues . DiffPatchFilePath ) ,
2018-05-08 09:55:38 +03:00
processors . NewMaxPerFileFromLinter ( ) ,
2018-05-11 22:01:57 +03:00
processors . NewMaxSameIssues ( e . cfg . Issues . MaxSameIssues ) ,
processors . NewMaxFromLinter ( e . cfg . Issues . MaxIssuesPerLinter ) ,
2018-05-06 19:08:34 +03:00
} ,
}
2018-05-05 19:43:18 +03:00
2018-05-07 21:44:40 +03:00
return runner . Run ( ctx , linters , lintCtx ) , nil
2018-05-06 19:08:34 +03:00
}
2018-05-05 19:43:18 +03:00
2018-05-26 19:58:17 +03:00
func setOutputToDevNull ( ) ( savedStdout , savedStderr * os . File ) {
savedStdout , savedStderr = os . Stdout , os . Stderr
devNull , err := os . Open ( os . DevNull )
if err != nil {
logrus . Warnf ( "can't open null device %q: %s" , os . DevNull , err )
return
}
os . Stdout , os . Stderr = devNull , devNull
return
}
2018-05-12 10:13:37 +03:00
func ( e * Executor ) runAndPrint ( ctx context . Context , args [ ] string ) error {
2018-05-26 19:58:17 +03:00
// Don't allow linters and loader to print anything
log . SetOutput ( ioutil . Discard )
savedStdout , savedStderr := setOutputToDevNull ( )
defer func ( ) {
os . Stdout , os . Stderr = savedStdout , savedStderr
} ( )
2018-05-12 10:13:37 +03:00
issues , err := e . runAnalysis ( ctx , args )
if err != nil {
return err
}
2018-05-06 13:25:50 +03:00
2018-05-12 10:13:37 +03:00
var p printers . Printer
if e . cfg . Output . Format == config . OutFormatJSON {
p = printers . NewJSON ( )
} else {
p = printers . NewText ( e . cfg . Output . PrintIssuedLine ,
e . cfg . Output . Format == config . OutFormatColoredLineNumber , e . cfg . Output . PrintLinterName )
}
gotAnyIssues , err := p . Print ( issues )
if err != nil {
return fmt . Errorf ( "can't print %d issues: %s" , len ( issues ) , err )
}
2018-05-07 21:44:40 +03:00
2018-05-12 10:13:37 +03:00
if gotAnyIssues {
e . exitCode = e . cfg . Run . ExitCodeIfIssuesFound
return nil
2018-05-08 17:13:16 +03:00
}
2018-05-12 10:13:37 +03:00
return nil
}
2018-05-05 09:24:37 +03:00
2018-05-12 10:13:37 +03:00
func ( e * Executor ) executeRun ( cmd * cobra . Command , args [ ] string ) {
2018-05-27 10:18:35 +03:00
logrus . Infof ( "Concurrency: %d, machine cpus count: %d" ,
e . cfg . Run . Concurrency , runtime . NumCPU ( ) )
2018-05-12 10:13:37 +03:00
needTrackResources := e . cfg . Run . IsVerbose || e . cfg . Run . PrintResourcesUsage
trackResourcesEndCh := make ( chan struct { } )
defer func ( ) { // XXX: this defer must be before ctx.cancel defer
if needTrackResources { // wait until resource tracking finished to print properly
<- trackResourcesEndCh
2018-05-05 09:24:37 +03:00
}
2018-05-12 10:13:37 +03:00
} ( )
2018-05-05 09:24:37 +03:00
2018-05-12 10:13:37 +03:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , e . cfg . Run . Deadline )
defer cancel ( )
2018-05-05 11:08:14 +03:00
2018-05-12 10:13:37 +03:00
if needTrackResources {
go watchResources ( ctx , trackResourcesEndCh )
}
if e . cfg . Output . PrintWelcomeMessage {
2018-05-26 19:58:17 +03:00
fmt . Fprintln ( printers . StdOut , "Run this tool in cloud on every github pull request in https://golangci.com for free (public repos)" )
2018-05-05 09:24:37 +03:00
}
2018-05-12 10:13:37 +03:00
if err := e . runAndPrint ( ctx , args ) ; err != nil {
2018-05-26 19:58:17 +03:00
logrus . Warnf ( "running error: %s" , err )
2018-05-06 19:08:34 +03:00
if e . exitCode == 0 {
e . exitCode = exitCodeIfFailure
}
2018-05-05 09:24:37 +03:00
}
}
2018-05-11 22:01:57 +03:00
func ( e * Executor ) parseConfig ( cmd * cobra . Command ) {
2018-05-26 21:07:38 +03:00
// XXX: hack with double parsing to access "config" option here
2018-05-11 22:01:57 +03:00
if err := cmd . ParseFlags ( os . Args ) ; err != nil {
2018-05-12 10:13:37 +03:00
if err == pflag . ErrHelp {
return
}
2018-05-26 19:58:17 +03:00
logrus . Fatalf ( "Can't parse args: %s" , err )
2018-05-11 22:01:57 +03:00
}
if err := viper . BindPFlags ( cmd . Flags ( ) ) ; err != nil {
2018-05-26 19:58:17 +03:00
logrus . Fatalf ( "Can't bind cobra's flags to viper: %s" , err )
2018-05-11 22:01:57 +03:00
}
viper . SetEnvPrefix ( "GOLANGCI" )
viper . SetEnvKeyReplacer ( strings . NewReplacer ( "." , "_" ) )
viper . AutomaticEnv ( )
2018-05-19 22:09:20 +03:00
configFile := e . cfg . Run . Config
if e . cfg . Run . NoConfig && configFile != "" {
2018-05-26 19:58:17 +03:00
logrus . Fatal ( "can't combine option --config and --no-config" )
2018-05-19 22:09:20 +03:00
}
if e . cfg . Run . NoConfig {
return
}
2018-05-11 22:01:57 +03:00
if configFile == "" {
viper . SetConfigName ( ".golangci" )
viper . AddConfigPath ( "./" )
} else {
viper . SetConfigFile ( configFile )
}
2018-05-19 22:09:20 +03:00
e . parseConfigImpl ( )
}
func ( e * Executor ) parseConfigImpl ( ) {
2018-05-19 13:07:55 +03:00
commandLineConfig := * e . cfg // make copy
2018-05-11 22:01:57 +03:00
if err := viper . ReadInConfig ( ) ; err != nil {
if _ , ok := err . ( viper . ConfigFileNotFoundError ) ; ok {
return
}
2018-05-26 19:58:17 +03:00
logrus . Fatalf ( "Can't read viper config: %s" , err )
2018-05-11 22:01:57 +03:00
}
if err := viper . Unmarshal ( & e . cfg ) ; err != nil {
2018-05-26 19:58:17 +03:00
logrus . Fatalf ( "Can't unmarshal config by viper: %s" , err )
2018-05-11 22:01:57 +03:00
}
2018-05-12 10:13:37 +03:00
2018-05-19 13:07:55 +03:00
if err := e . validateConfig ( & commandLineConfig ) ; err != nil {
2018-05-26 19:58:17 +03:00
logrus . Fatal ( err )
2018-05-12 10:13:37 +03:00
}
}
2018-05-19 13:07:55 +03:00
func ( e * Executor ) validateConfig ( commandLineConfig * config . Config ) error {
2018-05-12 10:13:37 +03:00
c := e . cfg
if len ( c . Run . Args ) != 0 {
2018-05-19 13:07:55 +03:00
return errors . New ( "option run.args in config isn't supported now" )
2018-05-12 10:13:37 +03:00
}
2018-05-19 13:07:55 +03:00
if commandLineConfig . Run . CPUProfilePath == "" && c . Run . CPUProfilePath != "" {
2018-05-12 10:13:37 +03:00
return errors . New ( "option run.cpuprofilepath in config isn't allowed" )
}
2018-05-19 22:09:20 +03:00
if commandLineConfig . Run . MemProfilePath == "" && c . Run . MemProfilePath != "" {
return errors . New ( "option run.memprofilepath in config isn't allowed" )
}
2018-05-19 13:07:55 +03:00
if ! commandLineConfig . Run . IsVerbose && c . Run . IsVerbose {
2018-05-15 08:59:50 +03:00
return errors . New ( "can't set run.verbose option with config: only on command-line" )
}
2018-05-12 10:13:37 +03:00
return nil
}
func watchResources ( ctx context . Context , done chan struct { } ) {
startedAt := time . Now ( )
rssValues := [ ] uint64 { }
ticker := time . NewTicker ( 100 * time . Millisecond )
defer ticker . Stop ( )
for {
var m runtime . MemStats
runtime . ReadMemStats ( & m )
rssValues = append ( rssValues , m . Sys )
stop := false
select {
case <- ctx . Done ( ) :
stop = true
case <- ticker . C : // track every second
}
if stop {
break
}
}
var avg , max uint64
for _ , v := range rssValues {
avg += v
if v > max {
max = v
}
}
avg /= uint64 ( len ( rssValues ) )
const MB = 1024 * 1024
maxMB := float64 ( max ) / MB
logrus . Infof ( "Memory: %d samples, avg is %.1fMB, max is %.1fMB" ,
len ( rssValues ) , float64 ( avg ) / MB , maxMB )
logrus . Infof ( "Execution took %s" , time . Since ( startedAt ) )
close ( done )
2018-05-11 22:01:57 +03:00
}