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  }