2020-07-02 03:04:46 -04:00
|
|
|
// Copyright 2020 The Hugo Authors. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
// Package debug provides template functions to help debugging templates.
|
|
|
|
package debug
|
|
|
|
|
|
|
|
import (
|
2024-03-26 05:43:08 -04:00
|
|
|
"encoding/json"
|
2023-10-19 04:53:27 -04:00
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/bep/logg"
|
2023-04-20 05:27:55 -04:00
|
|
|
"github.com/spf13/cast"
|
|
|
|
"github.com/yuin/goldmark/util"
|
2020-07-02 03:04:46 -04:00
|
|
|
|
2023-10-31 04:25:28 -04:00
|
|
|
"github.com/gohugoio/hugo/common/hugo"
|
2020-07-02 03:04:46 -04:00
|
|
|
"github.com/gohugoio/hugo/deps"
|
|
|
|
)
|
|
|
|
|
|
|
|
// New returns a new instance of the debug-namespaced template functions.
|
|
|
|
func New(d *deps.Deps) *Namespace {
|
2024-06-24 06:49:35 -04:00
|
|
|
ns := &Namespace{}
|
2023-10-19 04:53:27 -04:00
|
|
|
if d.Log.Level() <= logg.LevelInfo {
|
2024-06-24 06:49:35 -04:00
|
|
|
ns.timers = make(map[string][]*timer)
|
2023-10-19 04:53:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ns.timers == nil {
|
|
|
|
return ns
|
|
|
|
}
|
|
|
|
|
|
|
|
l := d.Log.InfoCommand("timer")
|
|
|
|
|
|
|
|
d.BuildEndListeners.Add(func() {
|
2023-10-21 09:41:21 -04:00
|
|
|
type data struct {
|
2023-10-19 04:53:27 -04:00
|
|
|
Name string
|
|
|
|
Count int
|
2023-10-21 09:41:21 -04:00
|
|
|
Average time.Duration
|
|
|
|
Median time.Duration
|
2023-10-19 04:53:27 -04:00
|
|
|
Duration time.Duration
|
|
|
|
}
|
|
|
|
|
2023-10-21 09:41:21 -04:00
|
|
|
var timersSorted []data
|
2023-10-19 04:53:27 -04:00
|
|
|
|
2024-06-24 06:49:35 -04:00
|
|
|
for k, v := range ns.timers {
|
2023-10-19 04:53:27 -04:00
|
|
|
var total time.Duration
|
2023-10-21 09:41:21 -04:00
|
|
|
var median time.Duration
|
|
|
|
sort.Slice(v, func(i, j int) bool {
|
|
|
|
return v[i].elapsed < v[j].elapsed
|
|
|
|
})
|
|
|
|
if len(v) > 0 {
|
|
|
|
median = v[len(v)/2].elapsed
|
|
|
|
}
|
2023-10-19 04:53:27 -04:00
|
|
|
for _, t := range v {
|
|
|
|
// Stop any running timers.
|
|
|
|
t.Stop()
|
|
|
|
total += t.elapsed
|
2023-10-21 09:41:21 -04:00
|
|
|
|
2023-10-19 04:53:27 -04:00
|
|
|
}
|
2023-10-21 09:41:21 -04:00
|
|
|
average := total / time.Duration(len(v))
|
|
|
|
timersSorted = append(timersSorted, data{k, len(v), average, median, total})
|
2023-10-19 04:53:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(timersSorted, func(i, j int) bool {
|
|
|
|
// Sort it so the slowest gets printed last.
|
|
|
|
return timersSorted[i].Duration < timersSorted[j].Duration
|
|
|
|
})
|
|
|
|
|
|
|
|
for _, t := range timersSorted {
|
2023-10-21 09:41:21 -04:00
|
|
|
l.WithField("name", t.Name).WithField("count", t.Count).
|
|
|
|
WithField("duration", t.Duration).
|
|
|
|
WithField("average", t.Average).
|
|
|
|
WithField("median", t.Median).Logf("")
|
2023-10-19 04:53:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
ns.timers = make(map[string][]*timer)
|
|
|
|
})
|
|
|
|
|
|
|
|
return ns
|
2020-07-02 03:04:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Namespace provides template functions for the "debug" namespace.
|
|
|
|
type Namespace struct {
|
2023-10-19 04:53:27 -04:00
|
|
|
timersMu sync.Mutex
|
|
|
|
timers map[string][]*timer
|
2020-07-02 03:04:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dump returns a object dump of val as a string.
|
|
|
|
// Note that not every value passed to Dump will print so nicely, but
|
2022-12-21 07:11:08 -05:00
|
|
|
// we'll improve on that.
|
|
|
|
//
|
|
|
|
// We recommend using the "go" Chroma lexer to format the output
|
2020-07-02 03:04:46 -04:00
|
|
|
// nicely.
|
2022-12-21 07:11:08 -05:00
|
|
|
//
|
2020-07-02 03:04:46 -04:00
|
|
|
// Also note that the output from Dump may change from Hugo version to the next,
|
|
|
|
// so don't depend on a specific output.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) Dump(val any) string {
|
2024-03-26 05:43:08 -04:00
|
|
|
b, err := json.MarshalIndent(val, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return string(b)
|
2020-07-02 03:04:46 -04:00
|
|
|
}
|
2023-04-20 05:27:55 -04:00
|
|
|
|
|
|
|
// VisualizeSpaces returns a string with spaces replaced by a visible string.
|
|
|
|
func (ns *Namespace) VisualizeSpaces(val any) string {
|
|
|
|
s := cast.ToString(val)
|
|
|
|
return string(util.VisualizeSpaces([]byte(s)))
|
|
|
|
}
|
2023-10-19 04:53:27 -04:00
|
|
|
|
|
|
|
func (ns *Namespace) Timer(name string) Timer {
|
|
|
|
if ns.timers == nil {
|
|
|
|
return nopTimer
|
|
|
|
}
|
|
|
|
ns.timersMu.Lock()
|
|
|
|
defer ns.timersMu.Unlock()
|
|
|
|
t := &timer{start: time.Now()}
|
|
|
|
ns.timers[name] = append(ns.timers[name], t)
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
var nopTimer = nopTimerImpl{}
|
|
|
|
|
|
|
|
type nopTimerImpl struct{}
|
|
|
|
|
|
|
|
func (nopTimerImpl) Stop() string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Timer is a timer that can be stopped.
|
|
|
|
type Timer interface {
|
|
|
|
// Stop stops the timer and returns an empty string.
|
|
|
|
// Stop can be called multiple times, but only the first call will stop the timer.
|
|
|
|
// If Stop is not called, the timer will be stopped when the build ends.
|
|
|
|
Stop() string
|
|
|
|
}
|
|
|
|
|
|
|
|
type timer struct {
|
|
|
|
start time.Time
|
|
|
|
elapsed time.Duration
|
|
|
|
stopOnce sync.Once
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *timer) Stop() string {
|
|
|
|
t.stopOnce.Do(func() {
|
|
|
|
t.elapsed = time.Since(t.start)
|
|
|
|
})
|
|
|
|
// This is used in templates, we need to return something.
|
|
|
|
return ""
|
|
|
|
}
|
2023-10-31 04:25:28 -04:00
|
|
|
|
|
|
|
// Internal template func, used in tests only.
|
|
|
|
func (ns *Namespace) TestDeprecationInfo(item, alternative string) string {
|
|
|
|
v := hugo.CurrentVersion
|
|
|
|
hugo.Deprecate(item, alternative, v.String())
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Internal template func, used in tests only.
|
|
|
|
func (ns *Namespace) TestDeprecationWarn(item, alternative string) string {
|
|
|
|
v := hugo.CurrentVersion
|
|
|
|
v.Minor -= 6
|
|
|
|
hugo.Deprecate(item, alternative, v.String())
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Internal template func, used in tests only.
|
|
|
|
func (ns *Namespace) TestDeprecationErr(item, alternative string) string {
|
|
|
|
v := hugo.CurrentVersion
|
|
|
|
v.Minor -= 12
|
|
|
|
hugo.Deprecate(item, alternative, v.String())
|
|
|
|
return ""
|
|
|
|
}
|