github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/state/stats.go (about)

     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  }