зеркало из https://github.com/golang/tools.git
249 строки
6.5 KiB
Go
249 строки
6.5 KiB
Go
// Copyright 2023 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"go/token"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/tools/gopls/internal/filecache"
|
|
"golang.org/x/tools/gopls/internal/protocol"
|
|
"golang.org/x/tools/gopls/internal/protocol/command"
|
|
"golang.org/x/tools/gopls/internal/settings"
|
|
bugpkg "golang.org/x/tools/gopls/internal/util/bug"
|
|
versionpkg "golang.org/x/tools/gopls/internal/version"
|
|
"golang.org/x/tools/internal/event"
|
|
)
|
|
|
|
type stats struct {
|
|
app *Application
|
|
|
|
Anon bool `flag:"anon" help:"hide any fields that may contain user names, file names, or source code"`
|
|
}
|
|
|
|
func (s *stats) Name() string { return "stats" }
|
|
func (r *stats) Parent() string { return r.app.Name() }
|
|
func (s *stats) Usage() string { return "" }
|
|
func (s *stats) ShortHelp() string { return "print workspace statistics" }
|
|
|
|
func (s *stats) DetailedHelp(f *flag.FlagSet) {
|
|
fmt.Fprint(f.Output(), `
|
|
Load the workspace for the current directory, and output a JSON summary of
|
|
workspace information relevant to performance. As a side effect, this command
|
|
populates the gopls file cache for the current workspace.
|
|
|
|
By default, this command may include output that refers to the location or
|
|
content of user code. When the -anon flag is set, fields that may refer to user
|
|
code are hidden.
|
|
|
|
Example:
|
|
$ gopls stats -anon
|
|
`)
|
|
printFlagDefaults(f)
|
|
}
|
|
|
|
func (s *stats) Run(ctx context.Context, args ...string) error {
|
|
if s.app.Remote != "" {
|
|
// stats does not work with -remote.
|
|
// Other sessions on the daemon may interfere with results.
|
|
// Additionally, the type assertions in below only work if progress
|
|
// notifications bypass jsonrpc2 serialization.
|
|
return fmt.Errorf("the stats subcommand does not work with -remote")
|
|
}
|
|
|
|
if !s.app.Verbose {
|
|
event.SetExporter(nil) // don't log errors to stderr
|
|
}
|
|
|
|
stats := GoplsStats{
|
|
GOOS: runtime.GOOS,
|
|
GOARCH: runtime.GOARCH,
|
|
GOPLSCACHE: os.Getenv("GOPLSCACHE"),
|
|
GoVersion: runtime.Version(),
|
|
GoplsVersion: versionpkg.Version(),
|
|
GOPACKAGESDRIVER: os.Getenv("GOPACKAGESDRIVER"),
|
|
}
|
|
|
|
opts := s.app.options
|
|
s.app.options = func(o *settings.Options) {
|
|
if opts != nil {
|
|
opts(o)
|
|
}
|
|
o.VerboseWorkDoneProgress = true
|
|
}
|
|
|
|
// do executes a timed section of the stats command.
|
|
do := func(name string, f func() error) (time.Duration, error) {
|
|
start := time.Now()
|
|
fmt.Fprintf(os.Stderr, "%-30s", name+"...")
|
|
if err := f(); err != nil {
|
|
return time.Since(start), err
|
|
}
|
|
d := time.Since(start)
|
|
fmt.Fprintf(os.Stderr, "done (%v)\n", d)
|
|
return d, nil
|
|
}
|
|
|
|
var conn *connection
|
|
iwlDuration, err := do("Initializing workspace", func() (err error) {
|
|
conn, err = s.app.connect(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
select {
|
|
case <-conn.client.iwlDone:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.terminate(ctx)
|
|
|
|
stats.InitialWorkspaceLoadDuration = fmt.Sprint(iwlDuration)
|
|
|
|
// Gather bug reports produced by any process using
|
|
// this executable and persisted in the cache.
|
|
do("Gathering bug reports", func() error {
|
|
stats.CacheDir, stats.BugReports = filecache.BugReports()
|
|
if stats.BugReports == nil {
|
|
stats.BugReports = []bugpkg.Bug{} // non-nil for JSON
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if _, err := do("Querying memstats", func() error {
|
|
memStats, err := conn.executeCommand(ctx, &protocol.Command{
|
|
Command: command.MemStats.String(),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stats.MemStats = memStats.(command.MemStatsResult)
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := do("Querying workspace stats", func() error {
|
|
wsStats, err := conn.executeCommand(ctx, &protocol.Command{
|
|
Command: command.WorkspaceStats.String(),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stats.WorkspaceStats = wsStats.(command.WorkspaceStatsResult)
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := do("Collecting directory info", func() error {
|
|
var err error
|
|
stats.DirStats, err = findDirStats()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Filter JSON output to fields that are consistent with s.Anon.
|
|
okFields := make(map[string]interface{})
|
|
{
|
|
v := reflect.ValueOf(stats)
|
|
t := v.Type()
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if !token.IsExported(f.Name) {
|
|
continue
|
|
}
|
|
vf := v.FieldByName(f.Name)
|
|
if s.Anon && f.Tag.Get("anon") != "ok" && !vf.IsZero() {
|
|
// Fields that can be served with -anon must be explicitly marked as OK.
|
|
// But, if it's zero value, it's ok to print.
|
|
continue
|
|
}
|
|
okFields[f.Name] = vf.Interface()
|
|
}
|
|
}
|
|
data, err := json.MarshalIndent(okFields, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
os.Stdout.Write(data)
|
|
fmt.Println()
|
|
return nil
|
|
}
|
|
|
|
// GoplsStats holds information extracted from a gopls session in the current
|
|
// workspace.
|
|
//
|
|
// Fields that should be printed with the -anon flag should be explicitly
|
|
// marked as `anon:"ok"`. Only fields that cannot refer to user files or code
|
|
// should be marked as such.
|
|
type GoplsStats struct {
|
|
GOOS, GOARCH string `anon:"ok"`
|
|
GOPLSCACHE string
|
|
GoVersion string `anon:"ok"`
|
|
GoplsVersion string `anon:"ok"`
|
|
GOPACKAGESDRIVER string
|
|
InitialWorkspaceLoadDuration string `anon:"ok"` // in time.Duration string form
|
|
CacheDir string
|
|
BugReports []bugpkg.Bug
|
|
MemStats command.MemStatsResult `anon:"ok"`
|
|
WorkspaceStats command.WorkspaceStatsResult `anon:"ok"`
|
|
DirStats dirStats `anon:"ok"`
|
|
}
|
|
|
|
type dirStats struct {
|
|
Files int
|
|
TestdataFiles int
|
|
GoFiles int
|
|
ModFiles int
|
|
Dirs int
|
|
}
|
|
|
|
// findDirStats collects information about the current directory and its
|
|
// subdirectories.
|
|
func findDirStats() (dirStats, error) {
|
|
var ds dirStats
|
|
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
ds.Dirs++
|
|
} else {
|
|
ds.Files++
|
|
slashed := filepath.ToSlash(path)
|
|
switch {
|
|
case strings.Contains(slashed, "/testdata/") || strings.HasPrefix(slashed, "testdata/"):
|
|
ds.TestdataFiles++
|
|
case strings.HasSuffix(path, ".go"):
|
|
ds.GoFiles++
|
|
case strings.HasSuffix(path, ".mod"):
|
|
ds.ModFiles++
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
return ds, nil
|
|
}
|