Skip to content

Commit d44a094

Browse files
committed
gopls/internal/lsp/cmd: add a stats -anon flag to show anonymous data
Also alter Bug storage to serialize bugs as JSON. Change-Id: I58ef96181b6c233333d1dfff39f1587f3cc9dd35 Reviewed-on: https://go-review.googlesource.com/c/tools/+/497755 Run-TryBot: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent e106694 commit d44a094

File tree

6 files changed

+123
-28
lines changed

6 files changed

+123
-28
lines changed

gopls/internal/bug/bug.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"runtime/debug"
1818
"sort"
1919
"sync"
20+
"time"
2021
)
2122

2223
// PanicOnBugs controls whether to panic when bugs are reported.
@@ -32,12 +33,15 @@ var (
3233

3334
// A Bug represents an unexpected event or broken invariant. They are used for
3435
// capturing metadata that helps us understand the event.
36+
//
37+
// Bugs are JSON-serializable.
3538
type Bug struct {
36-
File string // file containing the call to bug.Report
37-
Line int // line containing the call to bug.Report
38-
Description string // description of the bug
39-
Key string // key identifying the bug (file:line if available)
40-
Stack string // call stack
39+
File string // file containing the call to bug.Report
40+
Line int // line containing the call to bug.Report
41+
Description string // description of the bug
42+
Key string // key identifying the bug (file:line if available)
43+
Stack string // call stack
44+
AtTime time.Time // time the bug was reported
4145
}
4246

4347
// Reportf reports a formatted bug message.
@@ -77,6 +81,7 @@ func report(description string) {
7781
Description: description,
7882
Key: key,
7983
Stack: string(debug.Stack()),
84+
AtTime: time.Now(),
8085
}
8186

8287
mu.Lock()

gopls/internal/bug/bug_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
package bug
66

77
import (
8+
"encoding/json"
89
"fmt"
910
"testing"
11+
"time"
12+
13+
"github.com/google/go-cmp/cmp"
1014
)
1115

1216
func resetForTesting() {
@@ -62,3 +66,26 @@ func TestBugHandler(t *testing.T) {
6266
t.Errorf("got %q, want %q", got, want)
6367
}
6468
}
69+
70+
func TestBugJSON(t *testing.T) {
71+
b1 := Bug{
72+
File: "foo.go",
73+
Line: 1,
74+
Description: "a bug",
75+
Key: "foo.go:1",
76+
Stack: "<stack>",
77+
AtTime: time.Now(),
78+
}
79+
80+
data, err := json.Marshal(b1)
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
var b2 Bug
85+
if err := json.Unmarshal(data, &b2); err != nil {
86+
t.Fatal(err)
87+
}
88+
if diff := cmp.Diff(b1, b2); diff != "" {
89+
t.Errorf("bugs differ after JSON Marshal/Unmarshal (-b1 +b2):\n%s", diff)
90+
}
91+
}

gopls/internal/lsp/cmd/stats.go

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import (
99
"encoding/json"
1010
"flag"
1111
"fmt"
12+
"go/token"
1213
"io/fs"
1314
"os"
1415
"path/filepath"
16+
"reflect"
1517
"runtime"
1618
"strings"
1719
"sync"
@@ -24,10 +26,13 @@ import (
2426
"golang.org/x/tools/gopls/internal/lsp/filecache"
2527
"golang.org/x/tools/gopls/internal/lsp/protocol"
2628
"golang.org/x/tools/gopls/internal/lsp/source"
29+
"golang.org/x/tools/internal/event"
2730
)
2831

2932
type stats struct {
3033
app *Application
34+
35+
Anon bool `flag:"anon" help:"hide any fields that may contain user names, file names, or source code"`
3136
}
3237

3338
func (s *stats) Name() string { return "stats" }
@@ -41,14 +46,17 @@ Load the workspace for the current directory, and output a JSON summary of
4146
workspace information relevant to performance. As a side effect, this command
4247
populates the gopls file cache for the current workspace.
4348
49+
By default, this command may include output that refers to the location or
50+
content of user code. When the -anon flag is set, fields that may refer to user
51+
code are hidden.
52+
4453
Example:
45-
$ gopls stats
54+
$ gopls stats -anon
4655
`)
4756
printFlagDefaults(f)
4857
}
4958

5059
func (s *stats) Run(ctx context.Context, args ...string) error {
51-
5260
// This undocumented environment variable allows
5361
// the cmd integration test to trigger a call to bug.Report.
5462
if msg := os.Getenv("TEST_GOPLS_BUG"); msg != "" {
@@ -65,6 +73,10 @@ func (s *stats) Run(ctx context.Context, args ...string) error {
6573
return fmt.Errorf("the stats subcommand does not work with -remote")
6674
}
6775

76+
if !s.app.Verbose {
77+
event.SetExporter(nil) // don't log errors to stderr
78+
}
79+
6880
stats := GoplsStats{
6981
GOOS: runtime.GOOS,
7082
GOARCH: runtime.GOARCH,
@@ -139,12 +151,10 @@ func (s *stats) Run(ctx context.Context, args ...string) error {
139151

140152
// Gather bug reports produced by any process using
141153
// this executable and persisted in the cache.
142-
stats.BugReports = []string{} // non-nil for JSON
143154
do("Gathering bug reports", func() error {
144-
cacheDir, reports := filecache.BugReports()
145-
stats.CacheDir = cacheDir
146-
for _, report := range reports {
147-
stats.BugReports = append(stats.BugReports, string(report))
155+
stats.CacheDir, stats.BugReports = filecache.BugReports()
156+
if stats.BugReports == nil {
157+
stats.BugReports = []goplsbug.Bug{} // non-nil for JSON
148158
}
149159
return nil
150160
})
@@ -186,25 +196,51 @@ func (s *stats) Run(ctx context.Context, args ...string) error {
186196
return err
187197
}
188198

189-
data, err := json.MarshalIndent(stats, "", " ")
199+
// Filter JSON output to fields that are consistent with s.Anon.
200+
okFields := make(map[string]interface{})
201+
{
202+
v := reflect.ValueOf(stats)
203+
t := v.Type()
204+
for i := 0; i < t.NumField(); i++ {
205+
f := t.Field(i)
206+
if !token.IsExported(f.Name) {
207+
continue
208+
}
209+
if s.Anon && f.Tag.Get("anon") != "ok" {
210+
// Fields that can be served with -anon must be explicitly marked as OK.
211+
continue
212+
}
213+
vf := v.FieldByName(f.Name)
214+
okFields[f.Name] = vf.Interface()
215+
}
216+
}
217+
data, err := json.MarshalIndent(okFields, "", " ")
190218
if err != nil {
191219
return err
192220
}
221+
193222
os.Stdout.Write(data)
194223
fmt.Println()
195224
return nil
196225
}
197226

227+
// GoplsStats holds information extracted from a gopls session in the current
228+
// workspace.
229+
//
230+
// Fields that should be printed with the -anon flag should be explicitly
231+
// marked as `anon:"ok"`. Only fields that cannot refer to user files or code
232+
// should be marked as such.
198233
type GoplsStats struct {
199-
GOOS, GOARCH, GOPLSCACHE string
200-
GoVersion string
201-
GoplsVersion string
202-
InitialWorkspaceLoadDuration string // in time.Duration string form
234+
GOOS, GOARCH string `anon:"ok"`
235+
GOPLSCACHE string
236+
GoVersion string `anon:"ok"`
237+
GoplsVersion string `anon:"ok"`
238+
InitialWorkspaceLoadDuration string `anon:"ok"` // in time.Duration string form
203239
CacheDir string
204-
BugReports []string
205-
MemStats command.MemStatsResult
206-
WorkspaceStats command.WorkspaceStatsResult
207-
DirStats dirStats
240+
BugReports []goplsbug.Bug
241+
MemStats command.MemStatsResult `anon:"ok"`
242+
WorkspaceStats command.WorkspaceStatsResult `anon:"ok"`
243+
DirStats dirStats `anon:"ok"`
208244
}
209245

210246
type dirStats struct {

gopls/internal/lsp/cmd/test/integration_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,18 @@ package foo
756756
}
757757
}
758758

759+
// Check that -anon suppresses fields containing user information.
760+
{
761+
res2 := gopls(t, tree, "stats", "-anon")
762+
res2.checkExit(true)
763+
var stats2 cmd.GoplsStats
764+
if err := json.Unmarshal([]byte(res2.stdout), &stats2); err != nil {
765+
t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
766+
}
767+
if got := len(stats2.BugReports); got > 0 {
768+
t.Errorf("Got %d bug reports with -anon, want 0. Reports:%+v", got, stats2.BugReports)
769+
}
770+
}
759771
}
760772

761773
// TestFix tests the 'fix' subcommand (../suggested_fix.go).

gopls/internal/lsp/cmd/usage/stats.hlp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,11 @@ Load the workspace for the current directory, and output a JSON summary of
77
workspace information relevant to performance. As a side effect, this command
88
populates the gopls file cache for the current workspace.
99

10+
By default, this command may include output that refers to the location or
11+
content of user code. When the -anon flag is set, fields that may refer to user
12+
code are hidden.
13+
1014
Example:
11-
$ gopls stats
15+
$ gopls stats -anon
16+
-anon
17+
hide any fields that may contain user names, file names, or source code

gopls/internal/lsp/filecache/filecache.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"bytes"
2525
"crypto/sha256"
2626
"encoding/hex"
27+
"encoding/json"
2728
"errors"
2829
"fmt"
2930
"io"
@@ -533,17 +534,21 @@ func init() {
533534
// Wait for cache init (bugs in tests happen early).
534535
_, _ = getCacheDir()
535536

536-
value := []byte(fmt.Sprintf("%s: %+v", time.Now().Format(time.RFC3339), bug))
537-
key := sha256.Sum256(value)
538-
_ = Set(bugKind, key, value)
537+
data, err := json.Marshal(bug)
538+
if err != nil {
539+
panic(fmt.Sprintf("error marshalling bug %+v: %v", bug, err))
540+
}
541+
542+
key := sha256.Sum256(data)
543+
_ = Set(bugKind, key, data)
539544
})
540545
}
541546

542547
// BugReports returns a new unordered array of the contents
543548
// of all cached bug reports produced by this executable.
544549
// It also returns the location of the cache directory
545550
// used by this process (or "" on initialization error).
546-
func BugReports() (string, [][]byte) {
551+
func BugReports() (string, []bug.Bug) {
547552
// To test this logic, run:
548553
// $ TEST_GOPLS_BUG=oops gopls stats # trigger a bug
549554
// $ gopls stats # list the bugs
@@ -552,7 +557,7 @@ func BugReports() (string, [][]byte) {
552557
if err != nil {
553558
return "", nil // ignore initialization errors
554559
}
555-
var result [][]byte
560+
var result []bug.Bug
556561
_ = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
557562
if err != nil {
558563
return nil // ignore readdir/stat errors
@@ -566,7 +571,11 @@ func BugReports() (string, [][]byte) {
566571
}
567572
content, err := Get(bugKind, key)
568573
if err == nil { // ignore read errors
569-
result = append(result, content)
574+
var b bug.Bug
575+
if err := json.Unmarshal(content, &b); err != nil {
576+
log.Printf("error marshalling bug %q: %v", string(content), err)
577+
}
578+
result = append(result, b)
570579
}
571580
}
572581
return nil

0 commit comments

Comments
 (0)