github.com/neohugo/neohugo@v0.123.8/tpl/debug/debug.go (about) 1 // Copyright 2020 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 debug provides template functions to help debugging templates. 15 package debug 16 17 import ( 18 "sort" 19 "sync" 20 "time" 21 22 "github.com/bep/logg" 23 "github.com/sanity-io/litter" 24 "github.com/spf13/cast" 25 "github.com/yuin/goldmark/util" 26 27 "github.com/neohugo/neohugo/common/neohugo" 28 "github.com/neohugo/neohugo/deps" 29 ) 30 31 // New returns a new instance of the debug-namespaced template functions. 32 func New(d *deps.Deps) *Namespace { 33 var timers map[string][]*timer 34 if d.Log.Level() <= logg.LevelInfo { 35 timers = make(map[string][]*timer) 36 } 37 ns := &Namespace{ 38 timers: timers, 39 } 40 41 if ns.timers == nil { 42 return ns 43 } 44 45 l := d.Log.InfoCommand("timer") 46 47 d.BuildEndListeners.Add(func() { 48 type data struct { 49 Name string 50 Count int 51 Average time.Duration 52 Median time.Duration 53 Duration time.Duration 54 } 55 56 var timersSorted []data 57 58 for k, v := range timers { 59 var total time.Duration 60 var median time.Duration 61 sort.Slice(v, func(i, j int) bool { 62 return v[i].elapsed < v[j].elapsed 63 }) 64 if len(v) > 0 { 65 median = v[len(v)/2].elapsed 66 } 67 for _, t := range v { 68 // Stop any running timers. 69 t.Stop() 70 total += t.elapsed 71 72 } 73 average := total / time.Duration(len(v)) 74 timersSorted = append(timersSorted, data{k, len(v), average, median, total}) 75 } 76 77 sort.Slice(timersSorted, func(i, j int) bool { 78 // Sort it so the slowest gets printed last. 79 return timersSorted[i].Duration < timersSorted[j].Duration 80 }) 81 82 for _, t := range timersSorted { 83 l.WithField("name", t.Name).WithField("count", t.Count). 84 WithField("duration", t.Duration). 85 WithField("average", t.Average). 86 WithField("median", t.Median).Logf("") 87 } 88 89 ns.timers = make(map[string][]*timer) 90 }) 91 92 return ns 93 } 94 95 // Namespace provides template functions for the "debug" namespace. 96 type Namespace struct { 97 timersMu sync.Mutex 98 timers map[string][]*timer 99 } 100 101 // Dump returns a object dump of val as a string. 102 // Note that not every value passed to Dump will print so nicely, but 103 // we'll improve on that. 104 // 105 // We recommend using the "go" Chroma lexer to format the output 106 // nicely. 107 // 108 // Also note that the output from Dump may change from Hugo version to the next, 109 // so don't depend on a specific output. 110 func (ns *Namespace) Dump(val any) string { 111 return litter.Sdump(val) 112 } 113 114 // VisualizeSpaces returns a string with spaces replaced by a visible string. 115 func (ns *Namespace) VisualizeSpaces(val any) string { 116 s := cast.ToString(val) 117 return string(util.VisualizeSpaces([]byte(s))) 118 } 119 120 func (ns *Namespace) Timer(name string) Timer { 121 if ns.timers == nil { 122 return nopTimer 123 } 124 ns.timersMu.Lock() 125 defer ns.timersMu.Unlock() 126 t := &timer{start: time.Now()} 127 ns.timers[name] = append(ns.timers[name], t) 128 return t 129 } 130 131 var nopTimer = nopTimerImpl{} 132 133 type nopTimerImpl struct{} 134 135 func (nopTimerImpl) Stop() string { 136 return "" 137 } 138 139 // Timer is a timer that can be stopped. 140 type Timer interface { 141 // Stop stops the timer and returns an empty string. 142 // Stop can be called multiple times, but only the first call will stop the timer. 143 // If Stop is not called, the timer will be stopped when the build ends. 144 Stop() string 145 } 146 147 type timer struct { 148 start time.Time 149 elapsed time.Duration 150 stopOnce sync.Once 151 } 152 153 func (t *timer) Stop() string { 154 t.stopOnce.Do(func() { 155 t.elapsed = time.Since(t.start) 156 }) 157 // This is used in templates, we need to return something. 158 return "" 159 } 160 161 // Internal template func, used in tests only. 162 func (ns *Namespace) TestDeprecationInfo(item, alternative string) string { 163 v := neohugo.CurrentVersion 164 neohugo.Deprecate(item, alternative, v.String()) 165 return "" 166 } 167 168 // Internal template func, used in tests only. 169 func (ns *Namespace) TestDeprecationWarn(item, alternative string) string { 170 v := neohugo.CurrentVersion 171 v.Minor -= 6 172 neohugo.Deprecate(item, alternative, v.String()) 173 return "" 174 } 175 176 // Internal template func, used in tests only. 177 func (ns *Namespace) TestDeprecationErr(item, alternative string) string { 178 v := neohugo.CurrentVersion 179 v.Minor -= 12 180 neohugo.Deprecate(item, alternative, v.String()) 181 return "" 182 }