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