cuelang.org/go@v0.13.0/cue/stats/stats.go (about) 1 // Copyright 2022 CUE 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 stats is an experimental package for getting statistics on CUE 16 // evaluations. 17 package stats 18 19 import ( 20 "strings" 21 "sync" 22 "text/template" 23 24 "cuelang.org/go/internal" 25 ) 26 27 // Counts holds counters for key events during a CUE evaluation. 28 // 29 // This is an experimental type and the contents may change without notice. 30 type Counts struct { 31 // Note that we can't use the public [cuecontext.EvalVersion] type 32 // as that would lead to an import cycle. We could use "int" but that's a bit odd. 33 // There's no harm in referencing an internal type in practice, given that 34 // the public type is a type alias for the internal type already. 35 36 // EvalVersion is the evaluator version which was used for the CUE evaluation, 37 // corresponding to one of the values under [cuelang.org/go/cue/cuecontext.EvalVersion]. 38 EvalVersion internal.EvaluatorVersion 39 40 // Operation counters 41 // 42 // These counters account for several key operations. 43 44 // Unifications counts the number of calls to adt.Unify 45 Unifications int64 46 47 // Disjuncts indicates the number of total disjuncts processed as part 48 // of a Unify operation. A unification with no | operator counts as a 49 // single disjunct, so Disjuncts is always greater than or equal to the 50 // number of Unifications. 51 // 52 // If Disjuncts is much larger than Unification, this may indicate room 53 // for optimization. In particular, most practical uses of disjunctions 54 // should allow for near-linear processing. 55 Disjuncts int64 56 57 // Conjuncts is an estimate of the number of conjunctions processed during 58 // the calls to Unify. This includes the conjuncts added in the compilation 59 // phase as well as the derivative conjuncts inserted from other nodes 60 // after following references. 61 // 62 // A number of Conjuncts much larger than Disjuncts may indicate non-linear 63 // algorithmic behavior. 64 Conjuncts int64 65 66 CloseIDElems int64 67 NumCloseIDs int64 68 69 // Buffer counters 70 // 71 // Each unification and disjunct operation is associated with an object 72 // with temporary buffers. Reuse of this buffer is critical for performance. 73 // The following counters track this. 74 75 Freed int64 // Number of buffers returned to the free pool. 76 Reused int64 // Number of times a buffer is reused instead of allocated. 77 Allocs int64 // Total number of allocated buffer objects. 78 Retained int64 // Number of times a buffer is retained upon finalization. 79 } 80 81 // TODO: None of the methods below protect against overflows or underflows. 82 // If those start happening in practice, or if the counters get large enough, 83 // add checks on each of the operations. 84 85 func (c *Counts) Add(other Counts) { 86 switch v, vo := c.EvalVersion, other.EvalVersion; { 87 case v == internal.EvalVersionUnset: 88 // The first time we add evaluator counts, we record the evaluator version being used. 89 if vo == internal.EvalVersionUnset { 90 panic("the first call to Counts.Add must provide an evaluator version") 91 } 92 c.EvalVersion = vo 93 case v != vo: 94 // Any further evaluator counts being added must match the same evaluator version. 95 // 96 // TODO(mvdan): this is currently not possible to enforce, as we collect stats globally 97 // via [adt.AddStats] which includes stats from contexts created with different versions. 98 // We likely need to refactor the collection of stats so that it is not global first. 99 100 // panic(fmt.Sprintf("cannot mix evaluator versions in Counts.Add: %v vs %v", v, vo)) 101 } 102 c.Unifications += other.Unifications 103 c.Conjuncts += other.Conjuncts 104 c.Disjuncts += other.Disjuncts 105 106 c.CloseIDElems += other.CloseIDElems 107 c.NumCloseIDs += other.NumCloseIDs 108 109 c.Freed += other.Freed 110 c.Retained += other.Retained 111 c.Reused += other.Reused 112 c.Allocs += other.Allocs 113 } 114 115 func (c Counts) Since(start Counts) Counts { 116 c.Unifications -= start.Unifications 117 c.Conjuncts -= start.Conjuncts 118 c.Disjuncts -= start.Disjuncts 119 c.CloseIDElems -= start.CloseIDElems 120 c.NumCloseIDs -= start.NumCloseIDs 121 122 c.Freed -= start.Freed 123 c.Retained -= start.Retained 124 c.Reused -= start.Reused 125 c.Allocs -= start.Allocs 126 127 return c 128 } 129 130 // Leaks reports the number of nodeContext structs leaked. These are typically 131 // benign, as they will just be garbage collected, as long as the pointer from 132 // the original nodes has been eliminated or the original nodes are also not 133 // referred to. But Leaks may have notable impact on performance, and thus 134 // should be avoided. 135 func (s Counts) Leaks() int64 { 136 return s.Allocs + s.Reused - s.Freed 137 } 138 139 var stats = sync.OnceValue(func() *template.Template { 140 return template.Must(template.New("stats").Parse(`{{"" -}} 141 142 Leaks: {{.Leaks}} 143 Freed: {{.Freed}} 144 Reused: {{.Reused}} 145 Allocs: {{.Allocs}} 146 Retain: {{.Retained}} 147 148 Unifications: {{.Unifications}} 149 Conjuncts: {{.Conjuncts}} 150 Disjuncts: {{.Disjuncts}}{{if .NumCloseIDs}} 151 152 CloseIDElems: {{.CloseIDElems}} 153 NumCloseIDs: {{.NumCloseIDs}}{{end}}`)) 154 }) 155 156 func (s Counts) String() string { 157 buf := &strings.Builder{} 158 err := stats().Execute(buf, s) 159 if err != nil { 160 panic(err) 161 } 162 return buf.String() 163 }