687 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			687 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package commands
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"crypto/sha256"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"runtime/pprof"
 | 
						|
	"runtime/trace"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/fatih/color"
 | 
						|
	"github.com/gofrs/flock"
 | 
						|
	"github.com/spf13/cobra"
 | 
						|
	"github.com/spf13/pflag"
 | 
						|
	"github.com/spf13/viper"
 | 
						|
	"go.uber.org/automaxprocs/maxprocs"
 | 
						|
	"golang.org/x/exp/maps"
 | 
						|
	"gopkg.in/yaml.v3"
 | 
						|
 | 
						|
	"github.com/golangci/golangci-lint/internal/cache"
 | 
						|
	"github.com/golangci/golangci-lint/internal/pkgcache"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/config"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/exitcodes"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/fsutils"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/goutil"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/lint"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/lint/linter"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/logutils"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/printers"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/report"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/result"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/timeutils"
 | 
						|
)
 | 
						|
 | 
						|
const defaultTimeout = time.Minute
 | 
						|
 | 
						|
const (
 | 
						|
	// envFailOnWarnings value: "1"
 | 
						|
	envFailOnWarnings = "FAIL_ON_WARNINGS"
 | 
						|
	// envMemLogEvery value: "1"
 | 
						|
	envMemLogEvery = "GL_MEM_LOG_EVERY"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// envHelpRun value: "1".
 | 
						|
	envHelpRun        = "HELP_RUN"
 | 
						|
	envMemProfileRate = "GL_MEM_PROFILE_RATE"
 | 
						|
)
 | 
						|
 | 
						|
type runOptions struct {
 | 
						|
	config.LoaderOptions
 | 
						|
 | 
						|
	CPUProfilePath string // Flag only.
 | 
						|
	MemProfilePath string // Flag only.
 | 
						|
	TracePath      string // Flag only.
 | 
						|
 | 
						|
	PrintResourcesUsage bool // Flag only.
 | 
						|
}
 | 
						|
 | 
						|
type runCommand struct {
 | 
						|
	viper *viper.Viper
 | 
						|
	cmd   *cobra.Command
 | 
						|
 | 
						|
	opts runOptions
 | 
						|
 | 
						|
	cfg *config.Config
 | 
						|
 | 
						|
	buildInfo BuildInfo
 | 
						|
 | 
						|
	dbManager *lintersdb.Manager
 | 
						|
 | 
						|
	printer *printers.Printer
 | 
						|
 | 
						|
	log        logutils.Log
 | 
						|
	debugf     logutils.DebugFunc
 | 
						|
	reportData *report.Data
 | 
						|
 | 
						|
	contextLoader *lint.ContextLoader
 | 
						|
	goenv         *goutil.Env
 | 
						|
 | 
						|
	fileCache *fsutils.FileCache
 | 
						|
	lineCache *fsutils.LineCache
 | 
						|
 | 
						|
	flock *flock.Flock
 | 
						|
 | 
						|
	exitCode int
 | 
						|
}
 | 
						|
 | 
						|
func newRunCommand(logger logutils.Log, info BuildInfo) *runCommand {
 | 
						|
	reportData := &report.Data{}
 | 
						|
 | 
						|
	c := &runCommand{
 | 
						|
		viper:      viper.New(),
 | 
						|
		log:        report.NewLogWrapper(logger, reportData),
 | 
						|
		debugf:     logutils.Debug(logutils.DebugKeyExec),
 | 
						|
		cfg:        config.NewDefault(),
 | 
						|
		reportData: reportData,
 | 
						|
		buildInfo:  info,
 | 
						|
	}
 | 
						|
 | 
						|
	runCmd := &cobra.Command{
 | 
						|
		Use:                "run",
 | 
						|
		Short:              "Run the linters",
 | 
						|
		Run:                c.execute,
 | 
						|
		PreRunE:            c.preRunE,
 | 
						|
		PostRun:            c.postRun,
 | 
						|
		PersistentPreRunE:  c.persistentPreRunE,
 | 
						|
		PersistentPostRunE: c.persistentPostRunE,
 | 
						|
	}
 | 
						|
 | 
						|
	runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
 | 
						|
	runCmd.SetErr(logutils.StdErr)
 | 
						|
 | 
						|
	fs := runCmd.Flags()
 | 
						|
	fs.SortFlags = false // sort them as they are defined here
 | 
						|
 | 
						|
	// Only for testing purpose.
 | 
						|
	// Don't add other flags here.
 | 
						|
	fs.BoolVar(&c.cfg.InternalCmdTest, "internal-cmd-test", false,
 | 
						|
		color.GreenString("Option is used only for testing golangci-lint command, don't use it"))
 | 
						|
	_ = fs.MarkHidden("internal-cmd-test")
 | 
						|
 | 
						|
	setupConfigFileFlagSet(fs, &c.opts.LoaderOptions)
 | 
						|
 | 
						|
	setupLintersFlagSet(c.viper, fs)
 | 
						|
	setupRunFlagSet(c.viper, fs)
 | 
						|
	setupOutputFlagSet(c.viper, fs)
 | 
						|
	setupIssuesFlagSet(c.viper, fs)
 | 
						|
 | 
						|
	setupRunPersistentFlags(runCmd.PersistentFlags(), &c.opts)
 | 
						|
 | 
						|
	c.cmd = runCmd
 | 
						|
 | 
						|
	return c
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) persistentPreRunE(cmd *cobra.Command, _ []string) error {
 | 
						|
	if err := c.startTracing(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg)
 | 
						|
 | 
						|
	if err := loader.Load(); err != nil {
 | 
						|
		return fmt.Errorf("can't load config: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if c.cfg.Run.Concurrency == 0 {
 | 
						|
		backup := runtime.GOMAXPROCS(0)
 | 
						|
 | 
						|
		// Automatically set GOMAXPROCS to match Linux container CPU quota.
 | 
						|
		_, err := maxprocs.Set(maxprocs.Logger(c.log.Infof))
 | 
						|
		if err != nil {
 | 
						|
			runtime.GOMAXPROCS(backup)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		runtime.GOMAXPROCS(c.cfg.Run.Concurrency)
 | 
						|
	}
 | 
						|
 | 
						|
	return c.startTracing()
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) persistentPostRunE(_ *cobra.Command, _ []string) error {
 | 
						|
	if err := c.stopTracing(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	os.Exit(c.exitCode)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) preRunE(_ *cobra.Command, _ []string) error {
 | 
						|
	dbManager, err := lintersdb.NewManager(c.log.Child(logutils.DebugKeyLintersDB), c.cfg,
 | 
						|
		lintersdb.NewLinterBuilder(), lintersdb.NewPluginModuleBuilder(c.log), lintersdb.NewPluginGoBuilder(c.log))
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	c.dbManager = dbManager
 | 
						|
 | 
						|
	printer, err := printers.NewPrinter(c.log, &c.cfg.Output, c.reportData)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	c.printer = printer
 | 
						|
 | 
						|
	c.goenv = goutil.NewEnv(c.log.Child(logutils.DebugKeyGoEnv))
 | 
						|
 | 
						|
	c.fileCache = fsutils.NewFileCache()
 | 
						|
	c.lineCache = fsutils.NewLineCache(c.fileCache)
 | 
						|
 | 
						|
	sw := timeutils.NewStopwatch("pkgcache", c.log.Child(logutils.DebugKeyStopwatch))
 | 
						|
 | 
						|
	pkgCache, err := pkgcache.NewCache(sw, c.log.Child(logutils.DebugKeyPkgCache))
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to build packages cache: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	c.contextLoader = lint.NewContextLoader(c.cfg, c.log.Child(logutils.DebugKeyLoader), c.goenv,
 | 
						|
		c.lineCache, c.fileCache, pkgCache, load.NewGuard())
 | 
						|
 | 
						|
	if err = initHashSalt(c.buildInfo.Version, c.cfg); err != nil {
 | 
						|
		return fmt.Errorf("failed to init hash salt: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if ok := c.acquireFileLock(); !ok {
 | 
						|
		return errors.New("parallel golangci-lint is running")
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) postRun(_ *cobra.Command, _ []string) {
 | 
						|
	c.releaseFileLock()
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) execute(_ *cobra.Command, args []string) {
 | 
						|
	needTrackResources := logutils.IsVerbose() || c.opts.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
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	ctx, cancel := context.WithTimeout(context.Background(), c.cfg.Run.Timeout)
 | 
						|
	defer cancel()
 | 
						|
 | 
						|
	if needTrackResources {
 | 
						|
		go watchResources(ctx, trackResourcesEndCh, c.log, c.debugf)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := c.runAndPrint(ctx, args); err != nil {
 | 
						|
		c.log.Errorf("Running error: %s", err)
 | 
						|
		if c.exitCode == exitcodes.Success {
 | 
						|
			var exitErr *exitcodes.ExitError
 | 
						|
			if errors.As(err, &exitErr) {
 | 
						|
				c.exitCode = exitErr.Code
 | 
						|
			} else {
 | 
						|
				c.exitCode = exitcodes.Failure
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	c.setupExitCode(ctx)
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) startTracing() error {
 | 
						|
	if c.opts.CPUProfilePath != "" {
 | 
						|
		f, err := os.Create(c.opts.CPUProfilePath)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("can't create file %s: %w", c.opts.CPUProfilePath, err)
 | 
						|
		}
 | 
						|
		if err := pprof.StartCPUProfile(f); err != nil {
 | 
						|
			return fmt.Errorf("can't start CPU profiling: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if c.opts.MemProfilePath != "" {
 | 
						|
		if rate := os.Getenv(envMemProfileRate); rate != "" {
 | 
						|
			runtime.MemProfileRate, _ = strconv.Atoi(rate)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if c.opts.TracePath != "" {
 | 
						|
		f, err := os.Create(c.opts.TracePath)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("can't create file %s: %w", c.opts.TracePath, err)
 | 
						|
		}
 | 
						|
		if err = trace.Start(f); err != nil {
 | 
						|
			return fmt.Errorf("can't start tracing: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) stopTracing() error {
 | 
						|
	if c.opts.CPUProfilePath != "" {
 | 
						|
		pprof.StopCPUProfile()
 | 
						|
	}
 | 
						|
 | 
						|
	if c.opts.MemProfilePath != "" {
 | 
						|
		f, err := os.Create(c.opts.MemProfilePath)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("can't create file %s: %w", c.opts.MemProfilePath, err)
 | 
						|
		}
 | 
						|
 | 
						|
		var ms runtime.MemStats
 | 
						|
		runtime.ReadMemStats(&ms)
 | 
						|
		printMemStats(&ms, c.log)
 | 
						|
 | 
						|
		if err := pprof.WriteHeapProfile(f); err != nil {
 | 
						|
			return fmt.Errorf("can't write heap profile: %w", err)
 | 
						|
		}
 | 
						|
		_ = f.Close()
 | 
						|
	}
 | 
						|
 | 
						|
	if c.opts.TracePath != "" {
 | 
						|
		trace.Stop()
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) runAndPrint(ctx context.Context, args []string) error {
 | 
						|
	if err := c.goenv.Discover(ctx); err != nil {
 | 
						|
		c.log.Warnf("Failed to discover go env: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if !logutils.HaveDebugTag(logutils.DebugKeyLintersOutput) {
 | 
						|
		// Don't allow linters and loader to print anything
 | 
						|
		log.SetOutput(io.Discard)
 | 
						|
		savedStdout, savedStderr := c.setOutputToDevNull()
 | 
						|
		defer func() {
 | 
						|
			os.Stdout, os.Stderr = savedStdout, savedStderr
 | 
						|
		}()
 | 
						|
	}
 | 
						|
 | 
						|
	enabledLintersMap, err := c.dbManager.GetEnabledLintersMap()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	c.printDeprecatedLinterMessages(enabledLintersMap)
 | 
						|
 | 
						|
	issues, err := c.runAnalysis(ctx, args)
 | 
						|
	if err != nil {
 | 
						|
		return err // XXX: don't lose type
 | 
						|
	}
 | 
						|
 | 
						|
	// Fills linters information for the JSON printer.
 | 
						|
	for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() {
 | 
						|
		isEnabled := enabledLintersMap[lc.Name()] != nil
 | 
						|
		c.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault)
 | 
						|
	}
 | 
						|
 | 
						|
	err = c.printer.Print(issues)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	c.printStats(issues)
 | 
						|
 | 
						|
	c.setExitCodeIfIssuesFound(issues)
 | 
						|
 | 
						|
	c.fileCache.PrintStats(c.log)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// runAnalysis executes the linters that have been enabled in the configuration.
 | 
						|
func (c *runCommand) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
 | 
						|
	c.cfg.Run.Args = args
 | 
						|
 | 
						|
	lintersToRun, err := c.dbManager.GetOptimizedLinters()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	lintCtx, err := c.contextLoader.Load(ctx, c.log.Child(logutils.DebugKeyLintersContext), lintersToRun)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("context loading failed: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	runner, err := lint.NewRunner(c.log.Child(logutils.DebugKeyRunner),
 | 
						|
		c.cfg, c.goenv, c.lineCache, c.fileCache, c.dbManager, lintCtx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return runner.Run(ctx, lintersToRun)
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
 | 
						|
	savedStdout, savedStderr = os.Stdout, os.Stderr
 | 
						|
	devNull, err := os.Open(os.DevNull)
 | 
						|
	if err != nil {
 | 
						|
		c.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	os.Stdout, os.Stderr = devNull, devNull
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) setExitCodeIfIssuesFound(issues []result.Issue) {
 | 
						|
	if len(issues) != 0 {
 | 
						|
		c.exitCode = c.cfg.Run.ExitCodeIfIssuesFound
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) printDeprecatedLinterMessages(enabledLinters map[string]*linter.Config) {
 | 
						|
	if c.cfg.InternalCmdTest {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	for name, lc := range enabledLinters {
 | 
						|
		if !lc.IsDeprecated() {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		var extra string
 | 
						|
		if lc.Deprecation.Replacement != "" {
 | 
						|
			extra = fmt.Sprintf("Replaced by %s.", lc.Deprecation.Replacement)
 | 
						|
		}
 | 
						|
 | 
						|
		c.log.Warnf("The linter '%s' is deprecated (since %s) due to: %s %s", name, lc.Deprecation.Since, lc.Deprecation.Message, extra)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) printStats(issues []result.Issue) {
 | 
						|
	if !c.cfg.Output.ShowStats {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if len(issues) == 0 {
 | 
						|
		c.cmd.Println("0 issues.")
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	stats := map[string]int{}
 | 
						|
	for idx := range issues {
 | 
						|
		stats[issues[idx].FromLinter]++
 | 
						|
	}
 | 
						|
 | 
						|
	c.cmd.Printf("%d issues:\n", len(issues))
 | 
						|
 | 
						|
	keys := maps.Keys(stats)
 | 
						|
	sort.Strings(keys)
 | 
						|
 | 
						|
	for _, key := range keys {
 | 
						|
		c.cmd.Printf("* %s: %d\n", key, stats[key])
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) setupExitCode(ctx context.Context) {
 | 
						|
	if ctx.Err() != nil {
 | 
						|
		c.exitCode = exitcodes.Timeout
 | 
						|
		c.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option")
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if c.exitCode != exitcodes.Success {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	needFailOnWarnings := os.Getenv(logutils.EnvTestRun) == "1" || os.Getenv(envFailOnWarnings) == "1"
 | 
						|
	if needFailOnWarnings && len(c.reportData.Warnings) != 0 {
 | 
						|
		c.exitCode = exitcodes.WarningInTest
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if c.reportData.Error != "" {
 | 
						|
		// it's a case e.g. when typecheck linter couldn't parse and error and just logged it
 | 
						|
		c.exitCode = exitcodes.ErrorWasLogged
 | 
						|
		return
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) acquireFileLock() bool {
 | 
						|
	if c.cfg.Run.AllowParallelRunners {
 | 
						|
		c.debugf("Parallel runners are allowed, no locking")
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock")
 | 
						|
	c.debugf("Locking on file %s...", lockFile)
 | 
						|
	f := flock.New(lockFile)
 | 
						|
	const retryDelay = time.Second
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
	if !c.cfg.Run.AllowSerialRunners {
 | 
						|
		const totalTimeout = 5 * time.Second
 | 
						|
		var cancel context.CancelFunc
 | 
						|
		ctx, cancel = context.WithTimeout(ctx, totalTimeout)
 | 
						|
		defer cancel()
 | 
						|
	}
 | 
						|
	if ok, _ := f.TryLockContext(ctx, retryDelay); !ok {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	c.flock = f
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (c *runCommand) releaseFileLock() {
 | 
						|
	if c.cfg.Run.AllowParallelRunners {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err := c.flock.Unlock(); err != nil {
 | 
						|
		c.debugf("Failed to unlock on file: %s", err)
 | 
						|
	}
 | 
						|
	if err := os.Remove(c.flock.Path()); err != nil {
 | 
						|
		c.debugf("Failed to remove lock file: %s", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log, debugf logutils.DebugFunc) {
 | 
						|
	startedAt := time.Now()
 | 
						|
	debugf("Started tracking time")
 | 
						|
 | 
						|
	var maxRSSMB, totalRSSMB float64
 | 
						|
	var iterationsCount int
 | 
						|
 | 
						|
	const intervalMS = 100
 | 
						|
	ticker := time.NewTicker(intervalMS * time.Millisecond)
 | 
						|
	defer ticker.Stop()
 | 
						|
 | 
						|
	logEveryRecord := os.Getenv(envMemLogEvery) == "1"
 | 
						|
	const MB = 1024 * 1024
 | 
						|
 | 
						|
	track := func() {
 | 
						|
		var m runtime.MemStats
 | 
						|
		runtime.ReadMemStats(&m)
 | 
						|
 | 
						|
		if logEveryRecord {
 | 
						|
			debugf("Stopping memory tracing iteration, printing ...")
 | 
						|
			printMemStats(&m, logger)
 | 
						|
		}
 | 
						|
 | 
						|
		rssMB := float64(m.Sys) / MB
 | 
						|
		if rssMB > maxRSSMB {
 | 
						|
			maxRSSMB = rssMB
 | 
						|
		}
 | 
						|
		totalRSSMB += rssMB
 | 
						|
		iterationsCount++
 | 
						|
	}
 | 
						|
 | 
						|
	for {
 | 
						|
		track()
 | 
						|
 | 
						|
		stop := false
 | 
						|
		select {
 | 
						|
		case <-ctx.Done():
 | 
						|
			stop = true
 | 
						|
			debugf("Stopped resources tracking")
 | 
						|
		case <-ticker.C:
 | 
						|
		}
 | 
						|
 | 
						|
		if stop {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	track()
 | 
						|
 | 
						|
	avgRSSMB := totalRSSMB / float64(iterationsCount)
 | 
						|
 | 
						|
	logger.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB",
 | 
						|
		iterationsCount, avgRSSMB, maxRSSMB)
 | 
						|
	logger.Infof("Execution took %s", time.Since(startedAt))
 | 
						|
	close(done)
 | 
						|
}
 | 
						|
 | 
						|
func setupConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.LoaderOptions) {
 | 
						|
	fs.StringVarP(&cfg.Config, "config", "c", "", color.GreenString("Read config from file path `PATH`"))
 | 
						|
	fs.BoolVar(&cfg.NoConfig, "no-config", false, color.GreenString("Don't read config file"))
 | 
						|
}
 | 
						|
 | 
						|
func setupRunPersistentFlags(fs *pflag.FlagSet, opts *runOptions) {
 | 
						|
	fs.BoolVar(&opts.PrintResourcesUsage, "print-resources-usage", false,
 | 
						|
		color.GreenString("Print avg and max memory usage of golangci-lint and total time"))
 | 
						|
 | 
						|
	fs.StringVar(&opts.CPUProfilePath, "cpu-profile-path", "", color.GreenString("Path to CPU profile output file"))
 | 
						|
	fs.StringVar(&opts.MemProfilePath, "mem-profile-path", "", color.GreenString("Path to memory profile output file"))
 | 
						|
	fs.StringVar(&opts.TracePath, "trace-path", "", color.GreenString("Path to trace output file"))
 | 
						|
}
 | 
						|
 | 
						|
func getDefaultConcurrency() int {
 | 
						|
	if os.Getenv(envHelpRun) == "1" {
 | 
						|
		// Make stable concurrency for generating help documentation.
 | 
						|
		const prettyConcurrency = 8
 | 
						|
		return prettyConcurrency
 | 
						|
	}
 | 
						|
 | 
						|
	return runtime.NumCPU()
 | 
						|
}
 | 
						|
 | 
						|
func printMemStats(ms *runtime.MemStats, logger logutils.Log) {
 | 
						|
	logger.Infof("Mem stats: alloc=%s total_alloc=%s sys=%s "+
 | 
						|
		"heap_alloc=%s heap_sys=%s heap_idle=%s heap_released=%s heap_in_use=%s "+
 | 
						|
		"stack_in_use=%s stack_sys=%s "+
 | 
						|
		"mspan_sys=%s mcache_sys=%s buck_hash_sys=%s gc_sys=%s other_sys=%s "+
 | 
						|
		"mallocs_n=%d frees_n=%d heap_objects_n=%d gc_cpu_fraction=%.2f",
 | 
						|
		formatMemory(ms.Alloc), formatMemory(ms.TotalAlloc), formatMemory(ms.Sys),
 | 
						|
		formatMemory(ms.HeapAlloc), formatMemory(ms.HeapSys),
 | 
						|
		formatMemory(ms.HeapIdle), formatMemory(ms.HeapReleased), formatMemory(ms.HeapInuse),
 | 
						|
		formatMemory(ms.StackInuse), formatMemory(ms.StackSys),
 | 
						|
		formatMemory(ms.MSpanSys), formatMemory(ms.MCacheSys), formatMemory(ms.BuckHashSys),
 | 
						|
		formatMemory(ms.GCSys), formatMemory(ms.OtherSys),
 | 
						|
		ms.Mallocs, ms.Frees, ms.HeapObjects, ms.GCCPUFraction)
 | 
						|
}
 | 
						|
 | 
						|
func formatMemory(memBytes uint64) string {
 | 
						|
	const Kb = 1024
 | 
						|
	const Mb = Kb * 1024
 | 
						|
 | 
						|
	if memBytes < Kb {
 | 
						|
		return fmt.Sprintf("%db", memBytes)
 | 
						|
	}
 | 
						|
	if memBytes < Mb {
 | 
						|
		return fmt.Sprintf("%dkb", memBytes/Kb)
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%dmb", memBytes/Mb)
 | 
						|
}
 | 
						|
 | 
						|
// Related to cache.
 | 
						|
 | 
						|
func initHashSalt(version string, cfg *config.Config) error {
 | 
						|
	binSalt, err := computeBinarySalt(version)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to calculate binary salt: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	configSalt, err := computeConfigSalt(cfg)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to calculate config salt: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	b := bytes.NewBuffer(binSalt)
 | 
						|
	b.Write(configSalt)
 | 
						|
	cache.SetSalt(b.Bytes())
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func computeBinarySalt(version string) ([]byte, error) {
 | 
						|
	if version != "" && version != "(devel)" {
 | 
						|
		return []byte(version), nil
 | 
						|
	}
 | 
						|
 | 
						|
	if logutils.HaveDebugTag(logutils.DebugKeyBinSalt) {
 | 
						|
		return []byte("debug"), nil
 | 
						|
	}
 | 
						|
 | 
						|
	p, err := os.Executable()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	f, err := os.Open(p)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
	h := sha256.New()
 | 
						|
	if _, err := io.Copy(h, f); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return h.Sum(nil), nil
 | 
						|
}
 | 
						|
 | 
						|
// computeConfigSalt computes configuration hash.
 | 
						|
// We don't hash all config fields to reduce meaningless cache invalidations.
 | 
						|
// At least, it has a huge impact on tests speed.
 | 
						|
// Fields: `LintersSettings` and `Run.BuildTags`.
 | 
						|
func computeConfigSalt(cfg *config.Config) ([]byte, error) {
 | 
						|
	lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to json marshal config linter settings: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	configData := bytes.NewBufferString("linters-settings=")
 | 
						|
	configData.Write(lintersSettingsBytes)
 | 
						|
	configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ","))
 | 
						|
 | 
						|
	h := sha256.New()
 | 
						|
	if _, err := h.Write(configData.Bytes()); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return h.Sum(nil), nil
 | 
						|
}
 |