hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

metrics.go (7209B)

    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 	"reflect"
   22 	"sort"
   23 	"strconv"
   24 	"strings"
   25 	"sync"
   26 	"time"
   27 
   28 	"github.com/gohugoio/hugo/common/types"
   29 	"github.com/gohugoio/hugo/compare"
   30 	"github.com/gohugoio/hugo/helpers"
   31 )
   32 
   33 // The Provider interface defines an interface for measuring metrics.
   34 type Provider interface {
   35 	// MeasureSince adds a measurement for key to the metric store.
   36 	// Used with defer and time.Now().
   37 	MeasureSince(key string, start time.Time)
   38 
   39 	// WriteMetrics will write a summary of the metrics to w.
   40 	WriteMetrics(w io.Writer)
   41 
   42 	// TrackValue tracks the value for diff calculations etc.
   43 	TrackValue(key string, value any, cached bool)
   44 
   45 	// Reset clears the metric store.
   46 	Reset()
   47 }
   48 
   49 type diff struct {
   50 	baseline any
   51 	count    int
   52 	simSum   int
   53 }
   54 
   55 func (d *diff) add(v any) *diff {
   56 	if types.IsNil(d.baseline) {
   57 		d.baseline = v
   58 		d.count = 1
   59 		d.simSum = 100 // If we get only one it is very cache friendly.
   60 		return d
   61 	}
   62 	adder := howSimilar(v, d.baseline)
   63 	d.simSum += adder
   64 	d.count++
   65 
   66 	return d
   67 }
   68 
   69 // Store provides storage for a set of metrics.
   70 type Store struct {
   71 	calculateHints bool
   72 	metrics        map[string][]time.Duration
   73 	mu             sync.Mutex
   74 	diffs          map[string]*diff
   75 	diffmu         sync.Mutex
   76 	cached         map[string]int
   77 	cachedmu       sync.Mutex
   78 }
   79 
   80 // NewProvider returns a new instance of a metric store.
   81 func NewProvider(calculateHints bool) Provider {
   82 	return &Store{
   83 		calculateHints: calculateHints,
   84 		metrics:        make(map[string][]time.Duration),
   85 		diffs:          make(map[string]*diff),
   86 		cached:         make(map[string]int),
   87 	}
   88 }
   89 
   90 // Reset clears the metrics store.
   91 func (s *Store) Reset() {
   92 	s.mu.Lock()
   93 	s.metrics = make(map[string][]time.Duration)
   94 	s.mu.Unlock()
   95 
   96 	s.diffmu.Lock()
   97 	s.diffs = make(map[string]*diff)
   98 	s.diffmu.Unlock()
   99 
  100 	s.cachedmu.Lock()
  101 	s.cached = make(map[string]int)
  102 	s.cachedmu.Unlock()
  103 }
  104 
  105 // TrackValue tracks the value for diff calculations etc.
  106 func (s *Store) TrackValue(key string, value any, cached bool) {
  107 	if !s.calculateHints {
  108 		return
  109 	}
  110 
  111 	s.diffmu.Lock()
  112 	d, found := s.diffs[key]
  113 
  114 	if !found {
  115 		d = &diff{}
  116 		s.diffs[key] = d
  117 	}
  118 
  119 	d.add(value)
  120 	s.diffmu.Unlock()
  121 
  122 	if cached {
  123 		s.cachedmu.Lock()
  124 		s.cached[key] = s.cached[key] + 1
  125 		s.cachedmu.Unlock()
  126 	}
  127 }
  128 
  129 // MeasureSince adds a measurement for key to the metric store.
  130 func (s *Store) MeasureSince(key string, start time.Time) {
  131 	s.mu.Lock()
  132 	s.metrics[key] = append(s.metrics[key], time.Since(start))
  133 	s.mu.Unlock()
  134 }
  135 
  136 // WriteMetrics writes a summary of the metrics to w.
  137 func (s *Store) WriteMetrics(w io.Writer) {
  138 	s.mu.Lock()
  139 
  140 	results := make([]result, len(s.metrics))
  141 
  142 	var i int
  143 	for k, v := range s.metrics {
  144 		var sum time.Duration
  145 		var max time.Duration
  146 
  147 		diff, found := s.diffs[k]
  148 
  149 		cacheFactor := 0
  150 		if found {
  151 			cacheFactor = int(math.Floor(float64(diff.simSum) / float64(diff.count)))
  152 		}
  153 
  154 		for _, d := range v {
  155 			sum += d
  156 			if d > max {
  157 				max = d
  158 			}
  159 		}
  160 
  161 		avg := time.Duration(int(sum) / len(v))
  162 		cacheCount := s.cached[k]
  163 
  164 		results[i] = result{key: k, count: len(v), max: max, sum: sum, avg: avg, cacheCount: cacheCount, cacheFactor: cacheFactor}
  165 		i++
  166 	}
  167 
  168 	s.mu.Unlock()
  169 
  170 	if s.calculateHints {
  171 		fmt.Fprintf(w, "  %13s  %12s  %12s  %9s  %7s  %6s  %5s  %s\n", "cumulative", "average", "maximum", "cache", "percent", "cached", "total", "")
  172 		fmt.Fprintf(w, "  %13s  %12s  %12s  %9s  %7s  %6s  %5s  %s\n", "duration", "duration", "duration", "potential", "cached", "count", "count", "template")
  173 		fmt.Fprintf(w, "  %13s  %12s  %12s  %9s  %7s  %6s  %5s  %s\n", "----------", "--------", "--------", "---------", "-------", "------", "-----", "--------")
  174 	} else {
  175 		fmt.Fprintf(w, "  %13s  %12s  %12s  %5s  %s\n", "cumulative", "average", "maximum", "", "")
  176 		fmt.Fprintf(w, "  %13s  %12s  %12s  %5s  %s\n", "duration", "duration", "duration", "count", "template")
  177 		fmt.Fprintf(w, "  %13s  %12s  %12s  %5s  %s\n", "----------", "--------", "--------", "-----", "--------")
  178 
  179 	}
  180 
  181 	sort.Sort(bySum(results))
  182 	for _, v := range results {
  183 		if s.calculateHints {
  184 			fmt.Fprintf(w, "  %13s  %12s  %12s  %9d  %7.f  %6d  %5d  %s\n", v.sum, v.avg, v.max, v.cacheFactor, float64(v.cacheCount)/float64(v.count)*100, v.cacheCount, v.count, v.key)
  185 		} else {
  186 			fmt.Fprintf(w, "  %13s  %12s  %12s  %5d  %s\n", v.sum, v.avg, v.max, v.count, v.key)
  187 		}
  188 	}
  189 }
  190 
  191 // A result represents the calculated results for a given metric.
  192 type result struct {
  193 	key         string
  194 	count       int
  195 	cacheCount  int
  196 	cacheFactor int
  197 	sum         time.Duration
  198 	max         time.Duration
  199 	avg         time.Duration
  200 }
  201 
  202 type bySum []result
  203 
  204 func (b bySum) Len() int           { return len(b) }
  205 func (b bySum) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
  206 func (b bySum) Less(i, j int) bool { return b[i].sum > b[j].sum }
  207 
  208 // howSimilar is a naive diff implementation that returns
  209 // a number between 0-100 indicating how similar a and b are.
  210 func howSimilar(a, b any) int {
  211 	t1, t2 := reflect.TypeOf(a), reflect.TypeOf(b)
  212 	if t1 != t2 {
  213 		return 0
  214 	}
  215 
  216 	if t1.Comparable() && t2.Comparable() {
  217 		if a == b {
  218 			return 100
  219 		}
  220 	}
  221 
  222 	as, ok1 := types.TypeToString(a)
  223 	bs, ok2 := types.TypeToString(b)
  224 
  225 	if ok1 && ok2 {
  226 		return howSimilarStrings(as, bs)
  227 	}
  228 
  229 	if ok1 != ok2 {
  230 		return 0
  231 	}
  232 
  233 	e1, ok1 := a.(compare.Eqer)
  234 	e2, ok2 := b.(compare.Eqer)
  235 	if ok1 && ok2 && e1.Eq(e2) {
  236 		return 100
  237 	}
  238 
  239 	pe1, pok1 := a.(compare.ProbablyEqer)
  240 	pe2, pok2 := b.(compare.ProbablyEqer)
  241 	if pok1 && pok2 && pe1.ProbablyEq(pe2) {
  242 		return 90
  243 	}
  244 
  245 	h1, h2 := helpers.HashString(a), helpers.HashString(b)
  246 	if h1 == h2 {
  247 		return 100
  248 	}
  249 	return 0
  250 }
  251 
  252 // howSimilar is a naive diff implementation that returns
  253 // a number between 0-100 indicating how similar a and b are.
  254 // 100 is when all words in a also exists in b.
  255 func howSimilarStrings(a, b string) int {
  256 	if a == b {
  257 		return 100
  258 	}
  259 
  260 	// Give some weight to the word positions.
  261 	const partitionSize = 4
  262 
  263 	af, bf := strings.Fields(a), strings.Fields(b)
  264 	if len(bf) > len(af) {
  265 		af, bf = bf, af
  266 	}
  267 
  268 	m1 := make(map[string]bool)
  269 	for i, x := range bf {
  270 		partition := partition(i, partitionSize)
  271 		key := x + "/" + strconv.Itoa(partition)
  272 		m1[key] = true
  273 	}
  274 
  275 	common := 0
  276 	for i, x := range af {
  277 		partition := partition(i, partitionSize)
  278 		key := x + "/" + strconv.Itoa(partition)
  279 		if m1[key] {
  280 			common++
  281 		}
  282 	}
  283 
  284 	if common == 0 && common == len(af) {
  285 		return 100
  286 	}
  287 
  288 	return int(math.Floor((float64(common) / float64(len(af)) * 100)))
  289 }
  290 
  291 func partition(d, scale int) int {
  292 	return int(math.Floor((float64(d) / float64(scale)))) * scale
  293 }