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