github.com/olliephillips/hugo@v0.42.2/metrics/metrics.go (about) 1 // Copyright 2017 The Hugo Authors. All rights reserved. 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 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Package metrics provides simple metrics tracking features. 15 package metrics 16 17 import ( 18 "fmt" 19 "io" 20 "math" 21 "sort" 22 "strconv" 23 "strings" 24 "sync" 25 "time" 26 ) 27 28 // The Provider interface defines an interface for measuring metrics. 29 type Provider interface { 30 // MeasureSince adds a measurement for key to the metric store. 31 // Used with defer and time.Now(). 32 MeasureSince(key string, start time.Time) 33 34 // WriteMetrics will write a summary of the metrics to w. 35 WriteMetrics(w io.Writer) 36 37 // TrackValue tracks the value for diff calculations etc. 38 TrackValue(key, value string) 39 40 // Reset clears the metric store. 41 Reset() 42 } 43 44 type diff struct { 45 baseline string 46 count int 47 simSum int 48 } 49 50 func (d *diff) add(v string) *diff { 51 if d.baseline == "" { 52 d.baseline = v 53 d.count = 1 54 d.simSum = 100 // If we get only one it is very cache friendly. 55 return d 56 } 57 58 d.simSum += howSimilar(v, d.baseline) 59 d.count++ 60 61 return d 62 } 63 64 // Store provides storage for a set of metrics. 65 type Store struct { 66 calculateHints bool 67 metrics map[string][]time.Duration 68 mu sync.Mutex 69 diffs map[string]*diff 70 diffmu sync.Mutex 71 } 72 73 // NewProvider returns a new instance of a metric store. 74 func NewProvider(calculateHints bool) Provider { 75 return &Store{ 76 calculateHints: calculateHints, 77 metrics: make(map[string][]time.Duration), 78 diffs: make(map[string]*diff), 79 } 80 } 81 82 // Reset clears the metrics store. 83 func (s *Store) Reset() { 84 s.mu.Lock() 85 s.metrics = make(map[string][]time.Duration) 86 s.mu.Unlock() 87 s.diffmu.Lock() 88 s.diffs = make(map[string]*diff) 89 s.diffmu.Unlock() 90 } 91 92 // TrackValue tracks the value for diff calculations etc. 93 func (s *Store) TrackValue(key, value string) { 94 if !s.calculateHints { 95 return 96 } 97 98 s.diffmu.Lock() 99 var ( 100 d *diff 101 found bool 102 ) 103 104 d, found = s.diffs[key] 105 106 if !found { 107 d = &diff{} 108 s.diffs[key] = d 109 } 110 111 d.add(value) 112 s.diffmu.Unlock() 113 } 114 115 // MeasureSince adds a measurement for key to the metric store. 116 func (s *Store) MeasureSince(key string, start time.Time) { 117 s.mu.Lock() 118 s.metrics[key] = append(s.metrics[key], time.Since(start)) 119 s.mu.Unlock() 120 } 121 122 // WriteMetrics writes a summary of the metrics to w. 123 func (s *Store) WriteMetrics(w io.Writer) { 124 s.mu.Lock() 125 126 results := make([]result, len(s.metrics)) 127 128 var i int 129 for k, v := range s.metrics { 130 var sum time.Duration 131 var max time.Duration 132 133 diff, found := s.diffs[k] 134 cacheFactor := 0 135 if found { 136 cacheFactor = int(math.Floor(float64(diff.simSum) / float64(diff.count))) 137 } 138 139 for _, d := range v { 140 sum += d 141 if d > max { 142 max = d 143 } 144 } 145 146 avg := time.Duration(int(sum) / len(v)) 147 148 results[i] = result{key: k, count: len(v), max: max, sum: sum, avg: avg, cacheFactor: cacheFactor} 149 i++ 150 } 151 152 s.mu.Unlock() 153 154 if s.calculateHints { 155 fmt.Fprintf(w, " %9s %13s %12s %12s %5s %s\n", "cache", "cumulative", "average", "maximum", "", "") 156 fmt.Fprintf(w, " %9s %13s %12s %12s %5s %s\n", "potential", "duration", "duration", "duration", "count", "template") 157 fmt.Fprintf(w, " %9s %13s %12s %12s %5s %s\n", "-----", "----------", "--------", "--------", "-----", "--------") 158 } else { 159 fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "cumulative", "average", "maximum", "", "") 160 fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "duration", "duration", "duration", "count", "template") 161 fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "----------", "--------", "--------", "-----", "--------") 162 163 } 164 165 sort.Sort(bySum(results)) 166 for _, v := range results { 167 if s.calculateHints { 168 fmt.Fprintf(w, " %9d %13s %12s %12s %5d %s\n", v.cacheFactor, v.sum, v.avg, v.max, v.count, v.key) 169 } else { 170 fmt.Fprintf(w, " %13s %12s %12s %5d %s\n", v.sum, v.avg, v.max, v.count, v.key) 171 } 172 } 173 174 } 175 176 // A result represents the calculated results for a given metric. 177 type result struct { 178 key string 179 count int 180 cacheFactor int 181 sum time.Duration 182 max time.Duration 183 avg time.Duration 184 } 185 186 type bySum []result 187 188 func (b bySum) Len() int { return len(b) } 189 func (b bySum) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 190 func (b bySum) Less(i, j int) bool { return b[i].sum > b[j].sum } 191 192 // howSimilar is a naive diff implementation that returns 193 // a number between 0-100 indicating how similar a and b are. 194 // 100 is when all words in a also exists in b. 195 func howSimilar(a, b string) int { 196 197 if a == b { 198 return 100 199 } 200 201 // Give some weight to the word positions. 202 const partitionSize = 4 203 204 af, bf := strings.Fields(a), strings.Fields(b) 205 if len(bf) > len(af) { 206 af, bf = bf, af 207 } 208 209 m1 := make(map[string]bool) 210 for i, x := range bf { 211 partition := partition(i, partitionSize) 212 key := x + "/" + strconv.Itoa(partition) 213 m1[key] = true 214 } 215 216 common := 0 217 for i, x := range af { 218 partition := partition(i, partitionSize) 219 key := x + "/" + strconv.Itoa(partition) 220 if m1[key] { 221 common++ 222 } 223 } 224 225 return int(math.Floor((float64(common) / float64(len(af)) * 100))) 226 } 227 228 func partition(d, scale int) int { 229 return int(math.Floor((float64(d) / float64(scale)))) * scale 230 }