stats.go raw

   1  // Copyright 2018 The gVisor Authors.
   2  //
   3  // Licensed under the Apache License, Version 2.0 (the "License");
   4  // you may not use this file except in compliance with the License.
   5  // You may obtain a copy of the License at
   6  //
   7  //     http://www.apache.org/licenses/LICENSE-2.0
   8  //
   9  // Unless required by applicable law or agreed to in writing, software
  10  // distributed under the License is distributed on an "AS IS" BASIS,
  11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12  // See the License for the specific language governing permissions and
  13  // limitations under the License.
  14  
  15  package state
  16  
  17  import (
  18  	"bytes"
  19  	"fmt"
  20  	"sort"
  21  	"time"
  22  )
  23  
  24  type statEntry struct {
  25  	count uint
  26  	total time.Duration
  27  }
  28  
  29  // Stats tracks encode / decode timing.
  30  //
  31  // This currently provides a meaningful String function and no other way to
  32  // extract stats about individual types.
  33  //
  34  // All exported receivers accept nil.
  35  type Stats struct {
  36  	// byType contains a breakdown of time spent by type.
  37  	//
  38  	// This is indexed *directly* by typeID, including zero.
  39  	byType []statEntry
  40  
  41  	// stack contains objects in progress.
  42  	stack []typeID
  43  
  44  	// names contains type names.
  45  	//
  46  	// This is also indexed *directly* by typeID, including zero, which we
  47  	// hard-code as "state.default". This is only resolved by calling fini
  48  	// on the stats object.
  49  	names []string
  50  
  51  	// last is the last start time.
  52  	last time.Time
  53  }
  54  
  55  // init initializes statistics.
  56  func (s *Stats) init() {
  57  	s.last = time.Now()
  58  	s.stack = append(s.stack, 0)
  59  }
  60  
  61  // fini finalizes statistics.
  62  func (s *Stats) fini(resolve func(id typeID) string) {
  63  	s.done()
  64  
  65  	// Resolve all type names.
  66  	s.names = make([]string, len(s.byType))
  67  	s.names[0] = "state.default" // See above.
  68  	for id := typeID(1); int(id) < len(s.names); id++ {
  69  		s.names[id] = resolve(id)
  70  	}
  71  }
  72  
  73  // sample adds the samples to the given object.
  74  func (s *Stats) sample(id typeID) {
  75  	now := time.Now()
  76  	if len(s.byType) <= int(id) {
  77  		// Allocate all the missing entries in one fell swoop.
  78  		s.byType = append(s.byType, make([]statEntry, 1+int(id)-len(s.byType))...)
  79  	}
  80  	s.byType[id].total += now.Sub(s.last)
  81  	s.last = now
  82  }
  83  
  84  // start starts a sample.
  85  func (s *Stats) start(id typeID) {
  86  	last := s.stack[len(s.stack)-1]
  87  	s.sample(last)
  88  	s.stack = append(s.stack, id)
  89  }
  90  
  91  // done finishes the current sample.
  92  func (s *Stats) done() {
  93  	last := s.stack[len(s.stack)-1]
  94  	s.sample(last)
  95  	s.byType[last].count++
  96  	s.stack = s.stack[:len(s.stack)-1]
  97  }
  98  
  99  type sliceEntry struct {
 100  	name  string
 101  	entry *statEntry
 102  }
 103  
 104  // String returns a table representation of the stats.
 105  func (s *Stats) String() string {
 106  	// Build a list of stat entries.
 107  	ss := make([]sliceEntry, 0, len(s.byType))
 108  	for id := 0; id < len(s.names); id++ {
 109  		ss = append(ss, sliceEntry{
 110  			name:  s.names[id],
 111  			entry: &s.byType[id],
 112  		})
 113  	}
 114  
 115  	// Sort by total time (descending).
 116  	sort.Slice(ss, func(i, j int) bool {
 117  		return ss[i].entry.total > ss[j].entry.total
 118  	})
 119  
 120  	// Print the stat results.
 121  	var (
 122  		buf   bytes.Buffer
 123  		count uint
 124  		total time.Duration
 125  	)
 126  	buf.WriteString("\n")
 127  	buf.WriteString(fmt.Sprintf("% 16s | % 8s | % 16s | %s\n", "total", "count", "per", "type"))
 128  	buf.WriteString("-----------------+----------+------------------+----------------\n")
 129  	for _, se := range ss {
 130  		if se.entry.count == 0 {
 131  			// Since we store all types linearly, we are not
 132  			// guaranteed that any entry actually has time.
 133  			continue
 134  		}
 135  		count += se.entry.count
 136  		total += se.entry.total
 137  		per := se.entry.total / time.Duration(se.entry.count)
 138  		buf.WriteString(fmt.Sprintf("% 16s | %8d | % 16s | %s\n",
 139  			se.entry.total, se.entry.count, per, se.name))
 140  	}
 141  	buf.WriteString("-----------------+----------+------------------+----------------\n")
 142  	buf.WriteString(fmt.Sprintf("% 16s | % 8d | % 16s | [all]",
 143  		total, count, total/time.Duration(count)))
 144  	return string(buf.Bytes())
 145  }
 146