mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
e2dd4cd05f
commit
5160c7efa5
4 changed files with 157 additions and 10 deletions
9
deps/deps.go
vendored
9
deps/deps.go
vendored
|
@ -33,7 +33,6 @@ import (
|
||||||
// There will be normally only one instance of deps in play
|
// There will be normally only one instance of deps in play
|
||||||
// at a given time, i.e. one per Site built.
|
// at a given time, i.e. one per Site built.
|
||||||
type Deps struct {
|
type Deps struct {
|
||||||
|
|
||||||
// The logger to use.
|
// The logger to use.
|
||||||
Log loggers.Logger `json:"-"`
|
Log loggers.Logger `json:"-"`
|
||||||
|
|
||||||
|
@ -77,6 +76,9 @@ type Deps struct {
|
||||||
// BuildStartListeners will be notified before a build starts.
|
// BuildStartListeners will be notified before a build starts.
|
||||||
BuildStartListeners *Listeners
|
BuildStartListeners *Listeners
|
||||||
|
|
||||||
|
// BuildEndListeners will be notified after a build finishes.
|
||||||
|
BuildEndListeners *Listeners
|
||||||
|
|
||||||
// Resources that gets closed when the build is done or the server shuts down.
|
// Resources that gets closed when the build is done or the server shuts down.
|
||||||
BuildClosers *Closers
|
BuildClosers *Closers
|
||||||
|
|
||||||
|
@ -97,7 +99,6 @@ func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &d, nil
|
return &d, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
|
func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
|
||||||
|
@ -132,6 +133,10 @@ func (d *Deps) Init() error {
|
||||||
d.BuildStartListeners = &Listeners{}
|
d.BuildStartListeners = &Listeners{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.BuildEndListeners == nil {
|
||||||
|
d.BuildEndListeners = &Listeners{}
|
||||||
|
}
|
||||||
|
|
||||||
if d.BuildClosers == nil {
|
if d.BuildClosers == nil {
|
||||||
d.BuildClosers = &Closers{}
|
d.BuildClosers = &Closers{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -38,8 +39,6 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/output"
|
"github.com/gohugoio/hugo/output"
|
||||||
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
)
|
)
|
||||||
|
@ -68,6 +67,12 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||||
defer unlock()
|
defer unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, s := range h.Sites {
|
||||||
|
s.Deps.BuildEndListeners.Notify()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
infol := h.Log.InfoCommand("build")
|
infol := h.Log.InfoCommand("build")
|
||||||
|
|
||||||
errCollector := h.StartErrorCollector()
|
errCollector := h.StartErrorCollector()
|
||||||
|
@ -344,7 +349,6 @@ func (h *HugoSites) postRenderOnce() error {
|
||||||
h.Log.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
|
h.Log.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -384,7 +388,7 @@ func (h *HugoSites) postProcess(l logg.LevelLogger) error {
|
||||||
}
|
}
|
||||||
// Make sure it's written to the OS fs as this is used by
|
// Make sure it's written to the OS fs as this is used by
|
||||||
// editors.
|
// editors.
|
||||||
if err := afero.WriteFile(hugofs.Os, filename, b, 0666); err != nil {
|
if err := afero.WriteFile(hugofs.Os, filename, b, 0o666); err != nil {
|
||||||
h.Log.Warnf("Failed to write jsconfig.json: %s", err)
|
h.Log.Warnf("Failed to write jsconfig.json: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,7 +450,7 @@ func (h *HugoSites) postProcess(l logg.LevelLogger) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
return afero.WriteFile(h.BaseFs.PublishFs, filename, content, 0666)
|
return afero.WriteFile(h.BaseFs.PublishFs, filename, content, 0o666)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -512,13 +516,13 @@ func (h *HugoSites) writeBuildStats() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure it's always written to the OS fs.
|
// Make sure it's always written to the OS fs.
|
||||||
if err := afero.WriteFile(hugofs.Os, filename, js, 0666); err != nil {
|
if err := afero.WriteFile(hugofs.Os, filename, js, 0o666); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to the destination as well if it's a in-memory fs.
|
// Write to the destination as well if it's a in-memory fs.
|
||||||
if !hugofs.IsOsFs(h.Fs.Source) {
|
if !hugofs.IsOsFs(h.Fs.Source) {
|
||||||
if err := afero.WriteFile(h.Fs.WorkingDirWritable, filename, js, 0666); err != nil {
|
if err := afero.WriteFile(h.Fs.WorkingDirWritable, filename, js, 0o666); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,11 @@
|
||||||
package debug
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bep/logg"
|
||||||
"github.com/sanity-io/litter"
|
"github.com/sanity-io/litter"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
|
@ -24,11 +29,58 @@ import (
|
||||||
|
|
||||||
// New returns a new instance of the debug-namespaced template functions.
|
// New returns a new instance of the debug-namespaced template functions.
|
||||||
func New(d *deps.Deps) *Namespace {
|
func New(d *deps.Deps) *Namespace {
|
||||||
return &Namespace{}
|
var timers map[string][]*timer
|
||||||
|
if d.Log.Level() <= logg.LevelInfo {
|
||||||
|
timers = make(map[string][]*timer)
|
||||||
|
}
|
||||||
|
ns := &Namespace{
|
||||||
|
timers: timers,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ns.timers == nil {
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
l := d.Log.InfoCommand("timer")
|
||||||
|
|
||||||
|
d.BuildEndListeners.Add(func() {
|
||||||
|
type nameCountDuration struct {
|
||||||
|
Name string
|
||||||
|
Count int
|
||||||
|
Duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var timersSorted []nameCountDuration
|
||||||
|
|
||||||
|
for k, v := range timers {
|
||||||
|
var total time.Duration
|
||||||
|
for _, t := range v {
|
||||||
|
// Stop any running timers.
|
||||||
|
t.Stop()
|
||||||
|
total += t.elapsed
|
||||||
|
}
|
||||||
|
timersSorted = append(timersSorted, nameCountDuration{k, len(v), total})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
l.WithField("name", t.Name).WithField("count", t.Count).WithField("duration", t.Duration).Logf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
ns.timers = make(map[string][]*timer)
|
||||||
|
})
|
||||||
|
|
||||||
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace provides template functions for the "debug" namespace.
|
// Namespace provides template functions for the "debug" namespace.
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
|
timersMu sync.Mutex
|
||||||
|
timers map[string][]*timer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump returns a object dump of val as a string.
|
// Dump returns a object dump of val as a string.
|
||||||
|
@ -49,3 +101,44 @@ func (ns *Namespace) VisualizeSpaces(val any) string {
|
||||||
s := cast.ToString(val)
|
s := cast.ToString(val)
|
||||||
return string(util.VisualizeSpaces([]byte(s)))
|
return string(util.VisualizeSpaces([]byte(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
|
45
tpl/debug/integration_test.go
Normal file
45
tpl/debug/integration_test.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright 2023 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 requiredF 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bep/logg"
|
||||||
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimer(t *testing.T) {
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
baseURL = "https://example.org/"
|
||||||
|
disableKinds = ["taxonomy", "term"]
|
||||||
|
-- layouts/index.html --
|
||||||
|
{{ range seq 2 }}
|
||||||
|
{{ $t := debug.Timer "foo" }}
|
||||||
|
{{ seq 1 1000 }}
|
||||||
|
{{ $t.Stop }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
`
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
LogLevel: logg.LevelInfo,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
b.AssertLogContains("imer: name \"foo\" count '\\x02' duration")
|
||||||
|
}
|
Loading…
Reference in a new issue