blob: aebb1e2eb03644ed9b9bd7d8dad5262b89dec1de [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.
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.Type {
case ClRequest:
ans.ServerCall[l.ID] = l
case ClResponse:
ans.ServerReply[l.ID] = l
if l.Type != 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
l.Method = ans.ServerCall[l.ID].Method // Method not in log message
default:
log.Fatalf("eh? %s/%s (%s)", l.Type, 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()
}