blob: 35cd1d53e340a7ef92d3e1076ba010d2ce7c31d9 [file] [log] [blame]
// Copyright 2019 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.
// Replay logs. See README.md
package main
import (
"bufio"
"context"
"flag"
"fmt"
exec "golang.org/x/sys/execabs"
"log"
"os"
"sort"
"strconv"
"strings"
"golang.org/x/tools/gopls/integration/parse"
"golang.org/x/tools/internal/fakenet"
"golang.org/x/tools/internal/jsonrpc2"
p "golang.org/x/tools/internal/lsp/protocol"
)
var (
command = flag.String("cmd", "", "location of server to send to, looks for gopls")
cmp = flag.Bool("cmp", false, "only compare log and /tmp/seen")
logrdr *bufio.Scanner
msgs []*parse.Logmsg
// requests and responses/errors, by id
clreq = make(map[string]*parse.Logmsg)
clresp = make(map[string]*parse.Logmsg)
svreq = make(map[string]*parse.Logmsg)
svresp = make(map[string]*parse.Logmsg)
)
func main() {
log.SetFlags(log.Lshortfile)
flag.Usage = func() {
fmt.Fprintln(flag.CommandLine.Output(), "replay [options] <logfile>")
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
logf := flag.Arg(0)
orig, err := parse.ToRlog(logf)
if err != nil {
log.Fatalf("error parsing logfile %q: %v", logf, err)
}
ctx := context.Background()
msgs = orig.Logs
log.Printf("old %d, hist:%s", len(msgs), orig.Histogram)
if !*cmp {
log.Print("calling mimic")
mimic(ctx)
}
seen, err := parse.ToRlog("/tmp/seen")
if err != nil {
log.Fatal(err)
}
newMsgs := seen.Logs
log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
ok := make(map[string]int)
f := func(x []*parse.Logmsg, label string, diags map[p.DocumentURI][]p.Diagnostic) {
counts := make(map[parse.MsgType]int)
for _, l := range x {
if l.Method == "window/logMessage" {
// don't care
//continue
}
if l.Method == "textDocument/publishDiagnostics" {
v, ok := l.Body.(*p.PublishDiagnosticsParams)
if !ok {
log.Fatalf("got %T expected PublishDiagnosticsParams", l.Body)
}
diags[v.URI] = v.Diagnostics
}
counts[l.Type]++
// notifications only
if l.Type != parse.ToServer && l.Type != parse.ToClient {
continue
}
s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Type)
if i := strings.Index(s, "notification"); i != -1 {
s = s[i+12:]
}
if len(s) > 120 {
s = s[:120]
}
ok[s]++
}
msg := ""
for i := parse.ClRequest; i <= parse.ReportErr; i++ {
msg += fmt.Sprintf("%s:%d ", i, counts[i])
}
log.Printf("%s: %s", label, msg)
}
mdiags := make(map[p.DocumentURI][]p.Diagnostic)
f(msgs, "old", mdiags)
vdiags := make(map[p.DocumentURI][]p.Diagnostic)
f(newMsgs, "new", vdiags)
buf := []string{}
for k := range ok {
buf = append(buf, fmt.Sprintf("%s %d", k, ok[k]))
}
if len(buf) > 0 {
log.Printf("counts of notifications")
sort.Strings(buf)
for _, k := range buf {
log.Print(k)
}
}
buf = buf[0:0]
for k, v := range mdiags {
va := vdiags[k]
if len(v) != len(va) {
buf = append(buf, fmt.Sprintf("new has %d, old has %d for %s",
len(va), len(v), k))
}
}
for ka := range vdiags {
if _, ok := mdiags[ka]; !ok {
buf = append(buf, fmt.Sprintf("new diagnostics, but no old ones, for %s",
ka))
}
}
if len(buf) > 0 {
log.Print("diagnostics differ:")
for _, s := range buf {
log.Print(s)
}
}
}
func send(ctx context.Context, l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
if id == nil {
// need to use the number version of ID
n, err := strconv.Atoi(l.ID)
if err != nil {
n = 0
}
nid := jsonrpc2.NewIntID(int64(n))
id = &nid
}
var msg jsonrpc2.Message
var err error
switch l.Type {
case parse.ClRequest:
msg, err = jsonrpc2.NewCall(*id, l.Method, l.Body)
case parse.SvResponse:
msg, err = jsonrpc2.NewResponse(*id, l.Body, nil)
case parse.ToServer:
msg, err = jsonrpc2.NewNotification(l.Method, l.Body)
default:
log.Fatalf("sending %s", l.Type)
}
if err != nil {
log.Fatal(err)
}
stream.Write(ctx, msg)
}
func respond(ctx context.Context, c *jsonrpc2.Call, stream jsonrpc2.Stream) {
// c is a server request
// pick out the id, and look for the response in msgs
id := c.ID()
idstr := fmt.Sprint(id)
for _, l := range msgs {
if l.ID == idstr && l.Type == parse.SvResponse {
// check that the methods match?
// need to send back the same ID we got.
send(ctx, l, stream, &id)
return
}
}
log.Fatalf("no response found %q %+v %+v", c.Method(), c.ID(), c)
}
func findgopls() string {
totry := [][]string{{"GOBIN", "/gopls"}, {"GOPATH", "/bin/gopls"}, {"HOME", "/go/bin/gopls"}}
// looks in the places go install would install:
// GOBIN, else GOPATH/bin, else HOME/go/bin
ok := func(s string) bool {
fd, err := os.Open(s)
if err != nil {
return false
}
fi, err := fd.Stat()
if err != nil {
return false
}
return fi.Mode()&0111 != 0
}
for _, t := range totry {
g := os.Getenv(t[0])
if g != "" && ok(g+t[1]) {
gopls := g + t[1]
log.Printf("using gopls at %s", gopls)
return gopls
}
}
log.Fatal("could not find gopls")
return ""
}
func mimic(ctx context.Context) {
log.Printf("mimic %d", len(msgs))
if *command == "" {
*command = findgopls()
}
cmd := exec.Command(*command, "-logfile", "/tmp/seen", "-rpc.trace")
toServer, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
fromServer, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
conn := fakenet.NewConn("stdio", fromServer, toServer)
stream := jsonrpc2.NewHeaderStream(conn)
rchan := make(chan jsonrpc2.Message, 10) // do we need buffering?
rdr := func() {
for {
msg, _, err := stream.Read(ctx)
if err != nil {
rchan <- nil // close it instead?
return
}
rchan <- msg
}
}
go rdr()
// send as many as possible: all clrequests and toservers up to a clresponse
// and loop
seenids := make(map[string]bool) // id's that have been responded to:
big:
for _, l := range msgs {
switch l.Type {
case parse.ToServer: // just send these as we get to them
send(ctx, l, stream, nil)
case parse.ClRequest:
send(ctx, l, stream, nil) // for now, wait for a response, to make sure code is ok
fallthrough
case parse.ClResponse, parse.ReportErr: // don't go past these until they're received
if seenids[l.ID] {
break // onward, as it has been received already
}
done:
for {
msg := <-rchan
if msg == nil {
break big
}
// if it's svrequest, do something
// if it's clresponse or reporterr, add to seenids, and if it
// is l.id, break out of the loop, and continue the outer loop
switch msg := msg.(type) {
case *jsonrpc2.Call:
if parse.FromServer(msg.Method()) {
respond(ctx, msg, stream)
continue done // still waiting
}
case *jsonrpc2.Response:
id := fmt.Sprint(msg.ID())
seenids[id] = true
if id == l.ID {
break done
}
}
}
case parse.SvRequest: // not ours to send
continue
case parse.SvResponse: // sent by us, if the request arrives
continue
case parse.ToClient: // we don't send these
continue
}
}
}