| // 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. |
| |
| package parse |
| |
| import ( |
| "fmt" |
| "log" |
| "strings" |
| ) |
| |
| // Rlog contains the processed logs |
| type Rlog struct { |
| Logs []*Logmsg // In the order in the log file |
| ServerCall map[string]*Logmsg // ID->Request, client->server |
| ServerReply map[string]*Logmsg // ID->Response, server->client (includes Errors) |
| ClientCall map[string]*Logmsg |
| ClientReply map[string]*Logmsg |
| ClientNotifs []*Logmsg |
| ServerNotifs []*Logmsg |
| Histogram *LogHist |
| } |
| |
| func newRlog(x []*Logmsg) *Rlog { |
| return &Rlog{Logs: x, |
| ServerCall: make(map[string]*Logmsg), |
| ServerReply: make(map[string]*Logmsg), |
| ClientCall: make(map[string]*Logmsg), |
| ClientReply: make(map[string]*Logmsg), |
| ClientNotifs: []*Logmsg{}, |
| ServerNotifs: []*Logmsg{}, |
| Histogram: &LogHist{}, |
| } |
| } |
| |
| // Counts returns a one-line summary of an Rlog |
| func (r *Rlog) Counts() string { |
| return fmt.Sprintf("logs:%d srvC:%d srvR:%d clC:%d clR:%d clN:%d srvN:%d", |
| len(r.Logs), |
| len(r.ServerCall), len(r.ServerReply), len(r.ClientCall), len(r.ClientReply), |
| len(r.ClientNotifs), len(r.ServerNotifs)) |
| } |
| |
| // ToRlog reads a log file and returns a *Rlog |
| func ToRlog(fname string) (*Rlog, error) { |
| x, err := ReadLogs(fname) |
| if err != nil { |
| return nil, err |
| } |
| ans := newRlog(x) |
| for _, l := range x { |
| switch l.Dir { |
| case Clrequest: |
| ans.ServerCall[l.ID] = l |
| case Clresponse: |
| ans.ServerReply[l.ID] = l |
| if l.Dir != Reporterr { |
| n := 0 |
| fmt.Sscanf(l.Elapsed, "%d", &n) |
| ans.Histogram.add(n) |
| } |
| case Svrequest: |
| ans.ClientCall[l.ID] = l |
| case Svresponse: |
| ans.ClientReply[l.ID] = l |
| case Toclient: |
| ans.ClientNotifs = append(ans.ClientNotifs, l) |
| case Toserver: |
| ans.ServerNotifs = append(ans.ServerNotifs, l) |
| case Reporterr: |
| ans.ServerReply[l.ID] = l |
| default: |
| log.Fatalf("eh? %s/%s (%s)", l.Dir, l.Method, l.ID) |
| } |
| } |
| return ans, nil |
| } |
| |
| // LogHist gets ints, and puts them into buckets: |
| // <=10, <=30, 100, 300, 1000, ... |
| // It produces a historgram of elapsed times in milliseconds |
| type LogHist struct { |
| cnts []int |
| } |
| |
| func (l *LogHist) add(n int) { |
| if n < 0 { |
| n = 0 |
| } |
| bucket := 0 |
| for ; n > 0; n /= 10 { |
| if n < 10 { |
| break |
| } |
| if n < 30 { |
| bucket++ |
| break |
| } |
| bucket += 2 |
| } |
| if len(l.cnts) <= bucket { |
| for j := len(l.cnts); j < bucket+10; j++ { |
| l.cnts = append(l.cnts, 0) |
| } |
| } |
| l.cnts[bucket]++ |
| } |
| |
| // String returns a string describing a histogram |
| func (l *LogHist) String() string { |
| top := len(l.cnts) - 1 |
| for ; top > 0 && l.cnts[top] == 0; top-- { |
| } |
| labs := []string{"10", "30"} |
| out := strings.Builder{} |
| out.WriteByte('[') |
| for i := 0; i <= top; i++ { |
| label := labs[i%2] |
| labs[i%2] += "0" |
| fmt.Fprintf(&out, "%s:%d ", label, l.cnts[i]) |
| } |
| out.WriteByte(']') |
| return out.String() |
| } |