gopls/integration: cosmetic/ergonomic updates
Some ergonomic and cosmetic updates are made to the integration/replay
and integration/parse packages, following a pass through the code:
+ In the README, a some typos are corrected.
+ A few symbols are renamed:
- Direction->MsgType, since this type is more than a client-server
direction
- logRec->scanLogs, to be more consistent with the naming convention
SplitFuncs in the bufio package
- CamelCase is applied in a few places.
+ Context is plumbed through, rather than use a package local ctx.
+ In a few cases, returning an error is preferred over log.Fatal.
+ Some duplicated code is deleted, and types from the parse package are
reused. (this was probably cruft from the repo migration)
+ The logfile is made an argument rather than a flag to the replay
tool.
Change-Id: Ie72e6e8a4d7020d8cf708f6791353897791bcc86
Reviewed-on: https://go-review.googlesource.com/c/tools/+/211057
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/gopls/integration/parse/parse.go b/gopls/integration/parse/parse.go
index ae89957..0200c42 100644
--- a/gopls/integration/parse/parse.go
+++ b/gopls/integration/parse/parse.go
@@ -9,37 +9,37 @@
import (
"bufio"
"encoding/json"
+ "errors"
"fmt"
- "io"
"log"
"os"
"regexp"
"strings"
)
-// Direction is the type of message,
-type Direction int
+// MsgType is the type of message.
+type MsgType int
const (
- // Clrequest from client to server has method and id
- Clrequest Direction = iota
- // Clresponse from server to client
- Clresponse
- // Svrequest from server to client, has method and id
- Svrequest
- // Svresponse from client to server
- Svresponse
- // Toserver notification has method, but no id
- Toserver
- // Toclient notification
- Toclient
- // Reporterr is an error message
- Reporterr // errors have method and id
+ // ClRequest from client to server has method and id
+ ClRequest MsgType = iota
+ // ClResponse from server to client
+ ClResponse
+ // SvRequest from server to client, has method and id
+ SvRequest
+ // SvResponse from client to server
+ SvResponse
+ // ToServer notification has method, but no id
+ ToServer
+ // ToClient notification
+ ToClient
+ // ReportErr is an error message
+ ReportErr // errors have method and id
)
-// Logmsg is the type of a parsed log entry
+// Logmsg is the type of a parsed log entry.
type Logmsg struct {
- Dir Direction
+ Type MsgType
Method string
ID string // for requests/responses. Client and server request ids overlap
Elapsed string // for responses
@@ -48,7 +48,7 @@
Body interface{} // the parsed result
}
-// ReadLogs from a file. Most users should use TRlog().
+// ReadLogs from a file. Most users should use ToRlog().
func ReadLogs(fname string) ([]*Logmsg, error) {
byid := make(map[string]int)
msgs := []*Logmsg{}
@@ -56,39 +56,39 @@
if err != nil {
return nil, err
}
+ defer fd.Close()
logrdr := bufio.NewScanner(fd)
logrdr.Buffer(nil, 1<<25) // a large buffer, for safety
- logrdr.Split(logRec)
+ logrdr.Split(scanLogs)
for i := 0; logrdr.Scan(); i++ {
flds := strings.SplitN(logrdr.Text(), "\n", 2)
if len(flds) == 1 {
flds = append(flds, "") // for Errors
}
- msg := parselog(flds[0], flds[1])
- if msg == nil {
- log.Fatalf("failed to parse %q", logrdr.Text())
- continue
+ msg, err := parselog(flds[0], flds[1])
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse %q: %v", logrdr.Text(), err)
}
- switch msg.Dir {
- case Clrequest, Svrequest:
+ switch msg.Type {
+ case ClRequest, SvRequest:
v, err := msg.unmarshal(Requests(msg.Method))
if err != nil {
- log.Fatalf("%v for %s, %T", err, msg.Method, Requests(msg.Method))
+ return nil, fmt.Errorf("%v for %s, %T", err, msg.Method, Requests(msg.Method))
}
msg.Body = v
- case Clresponse, Svresponse:
+ case ClResponse, SvResponse:
v, err := msg.doresponse()
if err != nil {
- log.Fatalf("%v %s", err, msg.Method)
+ return nil, fmt.Errorf("%v %s", err, msg.Method)
}
msg.Body = v
- case Toserver, Toclient:
+ case ToServer, ToClient:
v, err := msg.unmarshal(Notifs(msg.Method))
if err != nil && Notifs(msg.Method) != nil {
- log.Fatalf("%s/%T: %v", msg.Method, Notifs(msg.Method), err)
+ return nil, fmt.Errorf("%s/%T: %v", msg.Method, Notifs(msg.Method), err)
}
msg.Body = v
- case Reporterr:
+ case ReportErr:
msg.Body = msg.ID // cause?
}
byid[msg.ID]++
@@ -101,13 +101,13 @@
}
// parse a single log message, given first line, and the rest
-func parselog(first, rest string) *Logmsg {
+func parselog(first, rest string) (*Logmsg, error) {
if strings.HasPrefix(rest, "Params: ") {
rest = rest[8:]
} else if strings.HasPrefix(rest, "Result: ") {
rest = rest[8:]
}
- ans := &Logmsg{Hdr: first, Rest: rest}
+ msg := &Logmsg{Hdr: first, Rest: rest}
fixid := func(s string) string {
// emacs does (n)., gopls does (n)'.
s = strings.Trim(s, "()'.{)")
@@ -118,45 +118,44 @@
// gopls and emacs differ in how they report elapsed time
switch {
case chk("Sending request", 9):
- ans.Dir = Clrequest
- ans.Method = flds[6][1:]
- ans.ID = fixid(flds[8][:len(flds[8])-2])
+ msg.Type = ClRequest
+ msg.Method = flds[6][1:]
+ msg.ID = fixid(flds[8][:len(flds[8])-2])
case chk("Received response", 11):
- ans.Dir = Clresponse
- ans.Method = flds[6][1:]
- ans.ID = fixid(flds[8])
- ans.Elapsed = flds[10]
+ msg.Type = ClResponse
+ msg.Method = flds[6][1:]
+ msg.ID = fixid(flds[8])
+ msg.Elapsed = flds[10]
case chk("Received request", 9):
- ans.Dir = Svrequest
- ans.Method = flds[6][1:]
- ans.ID = fixid(flds[8])
+ msg.Type = SvRequest
+ msg.Method = flds[6][1:]
+ msg.ID = fixid(flds[8])
case chk("Sending response", 11), // gopls
chk("Sending response", 13): // emacs
- ans.Dir = Svresponse
- ans.Method = flds[6][1:]
- ans.ID = fixid(flds[8][:len(flds[8])-1])
- ans.Elapsed = flds[10]
+ msg.Type = SvResponse
+ msg.Method = flds[6][1:]
+ msg.ID = fixid(flds[8][:len(flds[8])-1])
+ msg.Elapsed = flds[10]
case chk("Sending notification", 7):
- ans.Dir = Toserver
- ans.Method = strings.Trim(flds[6], ".'")
+ msg.Type = ToServer
+ msg.Method = strings.Trim(flds[6], ".'")
if len(flds) == 9 {
- log.Printf("len=%d method=%s %q", len(flds), ans.Method, first)
+ log.Printf("len=%d method=%s %q", len(flds), msg.Method, first)
}
case chk("Received notification", 7):
- ans.Dir = Toclient
- ans.Method = flds[6][1 : len(flds[6])-2]
+ msg.Type = ToClient
+ msg.Method = flds[6][1 : len(flds[6])-2]
case strings.HasPrefix(first, "[Error - "):
- ans.Dir = Reporterr
+ msg.Type = ReportErr
both := flds[5]
idx := strings.Index(both, "#") // relies on ID.Number
- ans.Method = both[:idx]
- ans.ID = fixid(both[idx+1:])
- ans.Rest = strings.Join(flds[6:], " ")
+ msg.Method = both[:idx]
+ msg.ID = fixid(both[idx+1:])
+ msg.Rest = strings.Join(flds[6:], " ")
default:
- log.Fatalf("surprise, first=%q with %d flds", first, len(flds))
- return nil
+ return nil, fmt.Errorf("surprise, first=%q with %d flds", first, len(flds))
}
- return ans
+ return msg, nil
}
// unmarshal into a proposed type
@@ -197,33 +196,33 @@
var recSep = regexp.MustCompile("\n\n\n|\r\n\r\n\r\n")
// return offset of start of next record, contents of record, error
-func logRec(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
+func scanLogs(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
got := recSep.FindIndex(b)
if got == nil {
- if !atEOF {
- return 0, nil, nil // need more
+ if atEOF && len(b) > 0 {
+ return 0, nil, errors.New("malformed log: all logs should end with a separator")
}
- return 0, nil, io.EOF
+ return 0, nil, nil
}
return got[1], b[:got[0]], nil
}
// String returns a user-useful versin of a Direction
-func (d Direction) String() string {
+func (d MsgType) String() string {
switch d {
- case Clrequest:
+ case ClRequest:
return "clrequest"
- case Clresponse:
+ case ClResponse:
return "clresponse"
- case Svrequest:
+ case SvRequest:
return "svrequest"
- case Svresponse:
+ case SvResponse:
return "svresponse"
- case Toserver:
+ case ToServer:
return "toserver"
- case Toclient:
+ case ToClient:
return "toclient"
- case Reporterr:
+ case ReportErr:
return "reporterr"
}
return fmt.Sprintf("dirname: %d unknown", d)
diff --git a/gopls/integration/parse/rlog.go b/gopls/integration/parse/rlog.go
index 00cd16c..58e752c 100644
--- a/gopls/integration/parse/rlog.go
+++ b/gopls/integration/parse/rlog.go
@@ -50,28 +50,28 @@
}
ans := newRlog(x)
for _, l := range x {
- switch l.Dir {
- case Clrequest:
+ switch l.Type {
+ case ClRequest:
ans.ServerCall[l.ID] = l
- case Clresponse:
+ case ClResponse:
ans.ServerReply[l.ID] = l
- if l.Dir != Reporterr {
+ if l.Type != ReportErr {
n := 0
fmt.Sscanf(l.Elapsed, "%d", &n)
ans.Histogram.add(n)
}
- case Svrequest:
+ case SvRequest:
ans.ClientCall[l.ID] = l
- case Svresponse:
+ case SvResponse:
ans.ClientReply[l.ID] = l
- case Toclient:
+ case ToClient:
ans.ClientNotifs = append(ans.ClientNotifs, l)
- case Toserver:
+ case ToServer:
ans.ServerNotifs = append(ans.ServerNotifs, l)
- case Reporterr:
+ case ReportErr:
ans.ServerReply[l.ID] = l
default:
- log.Fatalf("eh? %s/%s (%s)", l.Dir, l.Method, l.ID)
+ log.Fatalf("eh? %s/%s (%s)", l.Type, l.Method, l.ID)
}
}
return ans, nil
diff --git a/gopls/integration/replay/README.md b/gopls/integration/replay/README.md
index 3b29538..189b5b1 100644
--- a/gopls/integration/replay/README.md
+++ b/gopls/integration/replay/README.md
@@ -1,6 +1,6 @@
# Replaying Logs
-The LSP log replayer takes a log from a gopls session, starts up an instance of goppls,
+The LSP log replayer takes a log from a gopls session, starts up an instance of gopls,
and tries to replay the session. It produces a log from the replayed session and reports
some comparative statistics of the two logs.
@@ -48,7 +48,7 @@
the same number of errors *reporterr*. (That's mysterious, but a look at the ends of the log files shows
that the original session ended with several RPCs that don't show up, for whatever reason, in the new session.)
-Finally, there are counts of the various nofications seen, in the new log and the old log, and
+Finally, there are counts of the various notifications seen, in the new log and the old log, and
which direction they went. (The 3 fewer notifications in the summary above can be seen here to be from cancels
and a didChange.)
```
@@ -76,4 +76,4 @@
Gopls invokes various tools, and the environment they see could have changed too.
Replay will use the gopls it finds (or is given). It has no way of using
-the same version that created the original session.
\ No newline at end of file
+the same version that created the original session.
diff --git a/gopls/integration/replay/main.go b/gopls/integration/replay/main.go
index 6559418..cd30dd6 100644
--- a/gopls/integration/replay/main.go
+++ b/gopls/integration/replay/main.go
@@ -11,12 +11,9 @@
"encoding/json"
"flag"
"fmt"
- "io"
"log"
"os"
"os/exec"
- "regexp"
- "runtime"
"sort"
"strconv"
"strings"
@@ -27,47 +24,52 @@
)
var (
- ctx = context.Background()
command = flag.String("cmd", "", "location of server to send to, looks for gopls")
- logf = flag.String("log", "", "log file to replay")
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]*logmsg)
- clresp = make(map[string]*logmsg)
- svreq = make(map[string]*logmsg)
- svresp = make(map[string]*logmsg)
+ 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 *logf == "" {
- log.Fatal("need -log")
+ if flag.NArg() != 1 {
+ flag.Usage()
+ os.Exit(2)
}
+ logf := flag.Arg(0)
- orig, err := parse.ToRlog(*logf)
+ orig, err := parse.ToRlog(logf)
if err != nil {
- log.Fatalf("logfile %q %v", *logf, err)
+ 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()
+ mimic(ctx)
}
seen, err := parse.ToRlog("/tmp/seen")
if err != nil {
log.Fatal(err)
}
- vvv := seen.Logs
- log.Printf("new %d, hist:%s", len(vvv), seen.Histogram)
+ 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[string][]p.Diagnostic) {
- cnts := make(map[parse.Direction]int)
+ counts := make(map[parse.MsgType]int)
for _, l := range x {
if l.Method == "window/logMessage" {
// don't care
@@ -80,12 +82,12 @@
}
diags[v.URI] = v.Diagnostics
}
- cnts[l.Dir]++
+ counts[l.Type]++
// notifications only
- if l.Dir != parse.Toserver && l.Dir != parse.Toclient {
+ if l.Type != parse.ToServer && l.Type != parse.ToClient {
continue
}
- s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Dir)
+ 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:]
}
@@ -95,15 +97,15 @@
ok[s]++
}
msg := ""
- for i := parse.Clrequest; i <= parse.Reporterr; i++ {
- msg += fmt.Sprintf("%s:%d ", i, cnts[i])
+ 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[string][]p.Diagnostic)
f(msgs, "old", mdiags)
vdiags := make(map[string][]p.Diagnostic)
- f(vvv, "new", vdiags)
+ f(newMsgs, "new", vdiags)
buf := []string{}
for k := range ok {
buf = append(buf, fmt.Sprintf("%s %d", k, ok[k]))
@@ -137,74 +139,37 @@
}
}
-type direction int // what sort of message it is
-const (
- // rpc from client to server have method and id
- clrequest direction = iota
- clresponse
- // rpc from server have method and id
- svrequest
- svresponse
- // notifications have method, but no id
- toserver
- toclient
- reporterr // errors have method and id
-)
-
-// clrequest has method and id. toserver has method but no id, and svresponse has result (and id)
-type logmsg struct {
- dir direction
- method string
- id string // for requests/responses. Client and server request ids overlap
- elapsed string // for responses
- hdr string // do we need to keep all these strings?
- rest string // the unparsed result, with newlines or not
- body interface{} // the parsed(?) result
-}
-
-// combined has all the fields of both Request and Response.
-// Unmarshal this and then work out which it is.
-type combined struct {
- VersionTag jsonrpc2.VersionTag `json:"jsonrpc"`
- ID *jsonrpc2.ID `json:"id,omitempty"`
- // RPC name
- Method string `json:"method"`
- Params *json.RawMessage `json:"params,omitempty"`
- Result *json.RawMessage `json:"result,omitempty"`
- Error *jsonrpc2.Error `json:"error,omitempty"`
-}
-
-func (c *combined) dir() direction {
+func msgType(c *p.Combined) parse.MsgType {
// Method, Params, ID => request
// Method, Params, no-ID => notification
// Error => error response
// Result, ID => response
if c.Error != nil {
- return reporterr
+ return parse.ReportErr
}
if c.Params != nil && c.ID != nil {
// $/cancel could be either, cope someday
if parse.FromServer(c.Method) {
- return svrequest
+ return parse.SvRequest
}
- return clrequest
+ return parse.ClRequest
}
if c.Params != nil {
- // we're receiving it, so it must be toclient
- return toclient
+ // we're receiving it, so it must be ToClient
+ return parse.ToClient
}
if c.Result == nil {
if c.ID != nil {
- return clresponse
+ return parse.ClResponse
}
log.Printf("%+v", *c)
panic("couldn't determine direction")
}
- // we've received it, so it must be clresponse
- return clresponse
+ // we've received it, so it must be ClResponse
+ return parse.ClResponse
}
-func send(l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
+func send(ctx context.Context, l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
x, err := json.Marshal(l.Body)
if err != nil {
log.Fatal(err)
@@ -219,25 +184,25 @@
id = &jsonrpc2.ID{Number: int64(n)}
}
var r interface{}
- switch l.Dir {
- case parse.Clrequest:
+ switch l.Type {
+ case parse.ClRequest:
r = jsonrpc2.WireRequest{
ID: id,
Method: l.Method,
Params: &y,
}
- case parse.Svresponse:
+ case parse.SvResponse:
r = jsonrpc2.WireResponse{
ID: id,
Result: &y,
}
- case parse.Toserver:
+ case parse.ToServer:
r = jsonrpc2.WireRequest{
Method: l.Method,
Params: &y,
}
default:
- log.Fatalf("sending %s", l.Dir)
+ log.Fatalf("sending %s", l.Type)
}
data, err := json.Marshal(r)
if err != nil {
@@ -254,15 +219,15 @@
return strconv.Itoa(int(x.Number))
}
-func respond(c *combined, stream jsonrpc2.Stream) {
+func respond(ctx context.Context, c *p.Combined, stream jsonrpc2.Stream) {
// c is a server request
// pick out the id, and look for the response in msgs
id := strID(c.ID)
for _, l := range msgs {
- if l.ID == id && l.Dir == parse.Svresponse {
+ if l.ID == id && l.Type == parse.SvResponse {
// check that the methods match?
// need to send back the same ID we got.
- send(l, stream, c.ID)
+ send(ctx, l, stream, c.ID)
return
}
}
@@ -287,14 +252,16 @@
for _, t := range totry {
g := os.Getenv(t[0])
if g != "" && ok(g+t[1]) {
- return 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() {
+func mimic(ctx context.Context) {
log.Printf("mimic %d", len(msgs))
if *command == "" {
*command = findgopls()
@@ -313,7 +280,7 @@
log.Fatal(err)
}
stream := jsonrpc2.NewHeaderStream(fromServer, toServer)
- rchan := make(chan *combined, 10) // do we need buffering?
+ rchan := make(chan *p.Combined, 10) // do we need buffering?
rdr := func() {
for {
buf, _, err := stream.Read(ctx)
@@ -321,7 +288,7 @@
rchan <- nil // close it instead?
return
}
- msg := &combined{}
+ msg := &p.Combined{}
if err := json.Unmarshal(buf, msg); err != nil {
log.Fatal(err)
}
@@ -331,16 +298,16 @@
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 toig:
+ seenids := make(map[string]bool) // id's that have been responded to:
big:
for _, l := range msgs {
- switch l.Dir {
- case parse.Toserver: // just send these as we get to them
- send(l, stream, nil)
- case parse.Clrequest:
- send(l, stream, nil) // for now, wait for a response, to make sure code is ok
+ 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
+ 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
}
@@ -353,222 +320,28 @@
// 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 x.dir() {
- case svrequest:
- respond(x, stream)
+ switch mt := msgType(x); mt {
+ case parse.SvRequest:
+ respond(ctx, x, stream)
continue done // still waiting
- case clresponse, reporterr:
+ case parse.ClResponse, parse.ReportErr:
id := strID(x.ID)
seenids[id] = true
if id == l.ID {
break done
}
- case toclient:
+ case parse.ToClient:
continue
default:
- log.Fatalf("%s", x.dir())
+ log.Fatalf("%s", mt)
}
}
- case parse.Svrequest: // not ours to send
+ case parse.SvRequest: // not ours to send
continue
- case parse.Svresponse: // sent by us, if the request arrives
+ case parse.SvResponse: // sent by us, if the request arrives
continue
- case parse.Toclient: // we don't send these
+ case parse.ToClient: // we don't send these
continue
}
}
}
-
-func readLogs(fname string) []*logmsg {
- byid := make(map[string]int)
- msgs := []*logmsg{}
- fd, err := os.Open(fname)
- if err != nil {
- log.Fatal(err)
- }
- logrdr = bufio.NewScanner(fd)
- logrdr.Buffer(nil, 1<<25) // a large buffer, for safety
- logrdr.Split(logRec)
- for i := 0; logrdr.Scan(); i++ {
- flds := strings.SplitN(logrdr.Text(), "\n", 2)
- if len(flds) == 1 {
- flds = append(flds, "") // for Errors
- }
- msg := parselog(flds[0], flds[1])
- if msg == nil {
- log.Fatalf("failed to parse %q", logrdr.Text())
- continue
- }
- switch msg.dir {
- case clrequest, svrequest:
- v, err := msg.unmarshal(parse.Requests(msg.method))
- if err != nil {
- log.Fatal(err)
- }
- msg.body = v
- case clresponse, svresponse:
- v, err := msg.doresponse()
- if err != nil {
- log.Fatalf("%v %s", err, msg.method)
- }
- msg.body = v
- case toserver, toclient:
- v, err := msg.unmarshal(parse.Notifs(msg.method))
- if err != nil {
- log.Fatal(err)
- }
- msg.body = v
- case reporterr:
- msg.body = msg.id // cause?
- }
- byid[msg.id]++
- msgs = append(msgs, msg)
- }
- if err = logrdr.Err(); err != nil {
- log.Fatal(err)
- return msgs
- }
- // there's 2 uses of id 1, and notifications have no id
- for k, v := range byid {
- if false && v != 2 {
- log.Printf("ids %s:%d", k, v)
- }
- }
- if false {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
- log.Printf("%d msgs, alloc=%d HeapAlloc=%d", len(msgs), m.Alloc, m.HeapAlloc)
- }
- return msgs
-}
-
-func (d direction) String() string {
- switch d {
- case clrequest:
- return "clrequest"
- case clresponse:
- return "clresponse"
- case svrequest:
- return "svrequest"
- case svresponse:
- return "svresponse"
- case toserver:
- return "toserver"
- case toclient:
- return "toclient"
- case reporterr:
- return "reporterr"
- }
- return fmt.Sprintf("dirname: %d unknown", d)
-}
-
-func (l *logmsg) Short() string {
- return fmt.Sprintf("%s %s %s %s", l.dir, l.method, l.id, l.elapsed)
-}
-
-func (l *logmsg) unmarshal(p interface{}) (interface{}, error) {
- r := []byte(l.rest)
- if err := json.Unmarshal(r, p); err != nil {
- // need general alternatives, but for now
- // if p is *[]foo and rest is {}, return an empty p (or *p?)
- // or, cheat:
- if l.rest == "{}" {
- return nil, nil
- }
- return nil, err
- }
- return p, nil
-}
-
-func (l *logmsg) doresponse() (interface{}, error) {
- for _, x := range parse.Responses(l.method) {
- v, err := l.unmarshal(x)
- if err == nil {
- return v, nil
- }
- if x == nil {
- return new(interface{}), nil
- }
- }
- log.Fatalf("doresponse failed for %s", l.method)
- return nil, nil
-}
-
-// parse a single log message, given first line, and the rest
-func parselog(first, rest string) *logmsg {
- if strings.HasPrefix(rest, "Params: ") {
- rest = rest[8:]
- } else if strings.HasPrefix(rest, "Result: ") {
- rest = rest[8:]
- }
- ans := &logmsg{hdr: first, rest: rest}
- fixid := func(s string) string {
- if s != "" && s[0] == '(' {
- s = s[1 : len(s)-1]
- }
- return s
- }
- flds := strings.Fields(first)
- chk := func(s string, n int) bool { return strings.Contains(first, s) && len(flds) == n }
- // gopls and emacs differ in how they report elapsed time
- switch {
- case chk("Sending request", 9):
- ans.dir = clrequest
- ans.method = flds[6][1:]
- ans.id = fixid(flds[8][:len(flds[8])-2])
- clreq[ans.id] = ans
- case chk("Received response", 11):
- ans.dir = clresponse
- ans.method = flds[6][1:]
- ans.id = fixid(flds[8][:len(flds[8])-1])
- ans.elapsed = flds[10]
- clresp[ans.id] = ans
- case chk("Received request", 9):
- ans.dir = svrequest
- ans.method = flds[6][1:]
- ans.id = fixid(flds[8][:len(flds[8])-2])
- svreq[ans.id] = ans
- case chk("Sending response", 11), // gopls
- chk("Sending response", 13): // emacs
- ans.dir = svresponse
- ans.method = flds[6][1:]
- ans.id = fixid(flds[8][:len(flds[8])-1])
- ans.elapsed = flds[10]
- svresp[ans.id] = ans
- case chk("Sending notification", 7):
- ans.dir = toserver
- ans.method = strings.Trim(flds[6], ".'")
- if len(flds) == 9 {
- log.Printf("len=%d method=%s %q", len(flds), ans.method, first)
- }
- case chk("Received notification", 7):
- ans.dir = toclient
- ans.method = flds[6][1 : len(flds[6])-2]
- case strings.HasPrefix(first, "[Error - "):
- ans.dir = reporterr
- both := flds[5]
- idx := strings.Index(both, "#") // relies on ID.Number
- ans.method = both[:idx]
- ans.id = fixid(both[idx+1:])
- ans.rest = strings.Join(flds[6:], " ")
- clreq[ans.id] = ans
- default:
- log.Fatalf("surprise, first=%q with %d flds", first, len(flds))
- return nil
- }
- return ans
-}
-
-var recSep = regexp.MustCompile("\n\n\n|\r\n\r\n\r\n")
-
-// return start of next record, contents of record, error
-func logRec(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
- got := recSep.FindIndex(b)
- if got == nil {
- if !atEOF {
- return 0, nil, nil // need more
- }
- return 0, nil, io.EOF
- }
- return got[1], b[:got[0]], nil
-}