153 lines
3.5 KiB
Go
153 lines
3.5 KiB
Go
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package state
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
type statEntry struct {
|
|
count uint
|
|
total time.Duration
|
|
}
|
|
|
|
// Stats tracks encode / decode timing.
|
|
//
|
|
// This currently provides a meaningful String function and no other way to
|
|
// extract stats about individual types.
|
|
//
|
|
// All exported receivers accept nil.
|
|
type Stats struct {
|
|
// byType contains a breakdown of time spent by type.
|
|
byType map[reflect.Type]*statEntry
|
|
|
|
// stack contains objects in progress.
|
|
stack []reflect.Type
|
|
|
|
// last is the last start time.
|
|
last time.Time
|
|
}
|
|
|
|
// sample adds the samples to the given object.
|
|
func (s *Stats) sample(typ reflect.Type) {
|
|
now := time.Now()
|
|
s.byType[typ].total += now.Sub(s.last)
|
|
s.last = now
|
|
}
|
|
|
|
// Add adds a sample count.
|
|
func (s *Stats) Add(obj reflect.Value) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
if s.byType == nil {
|
|
s.byType = make(map[reflect.Type]*statEntry)
|
|
}
|
|
typ := obj.Type()
|
|
entry, ok := s.byType[typ]
|
|
if !ok {
|
|
entry = new(statEntry)
|
|
s.byType[typ] = entry
|
|
}
|
|
entry.count++
|
|
}
|
|
|
|
// Remove removes a sample count. It should only be called after a previous
|
|
// Add().
|
|
func (s *Stats) Remove(obj reflect.Value) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
typ := obj.Type()
|
|
entry := s.byType[typ]
|
|
entry.count--
|
|
}
|
|
|
|
// Start starts a sample.
|
|
func (s *Stats) Start(obj reflect.Value) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
if len(s.stack) > 0 {
|
|
last := s.stack[len(s.stack)-1]
|
|
s.sample(last)
|
|
} else {
|
|
// First time sample.
|
|
s.last = time.Now()
|
|
}
|
|
s.stack = append(s.stack, obj.Type())
|
|
}
|
|
|
|
// Done finishes the current sample.
|
|
func (s *Stats) Done() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
last := s.stack[len(s.stack)-1]
|
|
s.sample(last)
|
|
s.stack = s.stack[:len(s.stack)-1]
|
|
}
|
|
|
|
type sliceEntry struct {
|
|
typ reflect.Type
|
|
entry *statEntry
|
|
}
|
|
|
|
// String returns a table representation of the stats.
|
|
func (s *Stats) String() string {
|
|
if s == nil || len(s.byType) == 0 {
|
|
return "(no data)"
|
|
}
|
|
|
|
// Build a list of stat entries.
|
|
ss := make([]sliceEntry, 0, len(s.byType))
|
|
for typ, entry := range s.byType {
|
|
ss = append(ss, sliceEntry{
|
|
typ: typ,
|
|
entry: entry,
|
|
})
|
|
}
|
|
|
|
// Sort by total time (descending).
|
|
sort.Slice(ss, func(i, j int) bool {
|
|
return ss[i].entry.total > ss[j].entry.total
|
|
})
|
|
|
|
// Print the stat results.
|
|
var (
|
|
buf bytes.Buffer
|
|
count uint
|
|
total time.Duration
|
|
)
|
|
buf.WriteString("\n")
|
|
buf.WriteString(fmt.Sprintf("%12s | %8s | %8s | %s\n", "total", "count", "per", "type"))
|
|
buf.WriteString("-------------+----------+----------+-------------\n")
|
|
for _, se := range ss {
|
|
count += se.entry.count
|
|
total += se.entry.total
|
|
per := se.entry.total / time.Duration(se.entry.count)
|
|
buf.WriteString(fmt.Sprintf("%12s | %8d | %8s | %s\n",
|
|
se.entry.total, se.entry.count, per, se.typ.String()))
|
|
}
|
|
buf.WriteString("-------------+----------+----------+-------------\n")
|
|
buf.WriteString(fmt.Sprintf("%12s | %8d | %8s | [all]",
|
|
total, count, total/time.Duration(count)))
|
|
return string(buf.Bytes())
|
|
}
|