// 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" "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. // // This is indexed *directly* by typeID, including zero. byType []statEntry // stack contains objects in progress. stack []typeID // names contains type names. // // This is also indexed *directly* by typeID, including zero, which we // hard-code as "state.default". This is only resolved by calling fini // on the stats object. names []string // last is the last start time. last time.Time } // init initializes statistics. func (s *Stats) init() { s.last = time.Now() s.stack = append(s.stack, 0) } // fini finalizes statistics. func (s *Stats) fini(resolve func(id typeID) string) { s.done() // Resolve all type names. s.names = make([]string, len(s.byType)) s.names[0] = "state.default" // See above. for id := typeID(1); int(id) < len(s.names); id++ { s.names[id] = resolve(id) } } // sample adds the samples to the given object. func (s *Stats) sample(id typeID) { now := time.Now() if len(s.byType) <= int(id) { // Allocate all the missing entries in one fell swoop. s.byType = append(s.byType, make([]statEntry, 1+int(id)-len(s.byType))...) } s.byType[id].total += now.Sub(s.last) s.last = now } // start starts a sample. func (s *Stats) start(id typeID) { last := s.stack[len(s.stack)-1] s.sample(last) s.stack = append(s.stack, id) } // done finishes the current sample. func (s *Stats) done() { last := s.stack[len(s.stack)-1] s.sample(last) s.byType[last].count++ s.stack = s.stack[:len(s.stack)-1] } type sliceEntry struct { name string entry *statEntry } // String returns a table representation of the stats. func (s *Stats) String() string { // Build a list of stat entries. ss := make([]sliceEntry, 0, len(s.byType)) for id := 0; id < len(s.names); id++ { ss = append(ss, sliceEntry{ name: s.names[id], entry: &s.byType[id], }) } // 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("% 16s | % 8s | % 16s | %s\n", "total", "count", "per", "type")) buf.WriteString("-----------------+----------+------------------+----------------\n") for _, se := range ss { if se.entry.count == 0 { // Since we store all types linearly, we are not // guaranteed that any entry actually has time. continue } count += se.entry.count total += se.entry.total per := se.entry.total / time.Duration(se.entry.count) buf.WriteString(fmt.Sprintf("% 16s | %8d | % 16s | %s\n", se.entry.total, se.entry.count, per, se.name)) } buf.WriteString("-----------------+----------+------------------+----------------\n") buf.WriteString(fmt.Sprintf("% 16s | % 8d | % 16s | [all]", total, count, total/time.Duration(count))) return string(buf.Bytes()) }