Skip to content

Commit b4a14c2

Browse files
moorereasonbep
authored andcommitted
metrics: Add simple template metrics feature
1 parent cb8eb47 commit b4a14c2

File tree

7 files changed

+159
-3
lines changed

7 files changed

+159
-3
lines changed

‎commands/hugo.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ func initHugoBuildCommonFlags(cmd *cobra.Command) {
241241
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date and author info to the pages")
242242

243243
cmd.Flags().BoolVar(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program")
244+
cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
244245
cmd.Flags().Bool("pluralizeListTitles", true, "pluralize titles in lists using inflect")
245246
cmd.Flags().Bool("preserveTaxonomyNames", false, `preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")`)
246247
cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.")
@@ -475,6 +476,7 @@ func (c *commandeer) initializeFlags(cmd *cobra.Command) {
475476
"forceSyncStatic",
476477
"noTimes",
477478
"noChmod",
479+
"templateMetrics",
478480
}
479481

480482
// Remove these in Hugo 0.23.

‎deps/deps.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/gohugoio/hugo/config"
99
"github.com/gohugoio/hugo/helpers"
1010
"github.com/gohugoio/hugo/hugofs"
11+
"github.com/gohugoio/hugo/metrics"
1112
"github.com/gohugoio/hugo/output"
1213
"github.com/gohugoio/hugo/tpl"
1314
jww "github.com/spf13/jwalterweatherman"
@@ -47,6 +48,8 @@ type Deps struct {
4748
WithTemplate func(templ tpl.TemplateHandler) error `json:"-"`
4849

4950
translationProvider ResourceProvider
51+
52+
Metrics metrics.Provider
5053
}
5154

5255
// ResourceProvider is used to create and refresh, and clone resources needed.
@@ -131,6 +134,10 @@ func New(cfg DepsCfg) (*Deps, error) {
131134
Language: cfg.Language,
132135
}
133136

137+
if cfg.Cfg.GetBool("templateMetrics") {
138+
d.Metrics = metrics.NewProvider()
139+
}
140+
134141
return d, nil
135142
}
136143

‎hugolib/hugo_sites_build.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import (
2525
// Build builds all sites. If filesystem events are provided,
2626
// this is considered to be a potential partial rebuild.
2727
func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
28+
if h.Metrics != nil {
29+
h.Metrics.Reset()
30+
}
31+
2832
t0 := time.Now()
2933

3034
// Need a pointer as this may be modified.

‎hugolib/site.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package hugolib
1515

1616
import (
17+
"bytes"
1718
"errors"
1819
"fmt"
1920
"html/template"
@@ -1730,6 +1731,16 @@ func (s *Site) appendThemeTemplates(in []string) []string {
17301731
// Stats prints Hugo builds stats to the console.
17311732
// This is what you see after a successful hugo build.
17321733
func (s *Site) Stats() {
1734+
s.Log.FEEDBACK.Println()
1735+
1736+
if s.Cfg.GetBool("templateMetrics") {
1737+
var b bytes.Buffer
1738+
s.Metrics.WriteMetrics(&b)
1739+
1740+
s.Log.FEEDBACK.Printf("Template Metrics:\n\n")
1741+
s.Log.FEEDBACK.Print(b.String())
1742+
s.Log.FEEDBACK.Println()
1743+
}
17331744

17341745
s.Log.FEEDBACK.Printf("Built site for language %s:\n", s.Language.Lang)
17351746
s.Log.FEEDBACK.Println(s.draftStats())

‎metrics/metrics.go‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2017 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 metrics provides simple metrics tracking features.
15+
package metrics
16+
17+
import (
18+
"fmt"
19+
"io"
20+
"sort"
21+
"sync"
22+
"time"
23+
)
24+
25+
// The Provider interface defines an interface for measuring metrics.
26+
type Provider interface {
27+
// MeasureSince adds a measurement for key to the metric store.
28+
// Used with defer and time.Now().
29+
MeasureSince(key string, start time.Time)
30+
31+
// WriteMetrics will write a summary of the metrics to w.
32+
WriteMetrics(w io.Writer)
33+
34+
// Reset clears the metric store.
35+
Reset()
36+
}
37+
38+
// Store provides storage for a set of metrics.
39+
type Store struct {
40+
metrics map[string][]time.Duration
41+
mu *sync.Mutex
42+
}
43+
44+
// NewProvider returns a new instance of a metric store.
45+
func NewProvider() Provider {
46+
return &Store{
47+
metrics: make(map[string][]time.Duration),
48+
mu: &sync.Mutex{},
49+
}
50+
}
51+
52+
// Reset clears the metrics store.
53+
func (s *Store) Reset() {
54+
s.mu.Lock()
55+
s.metrics = make(map[string][]time.Duration)
56+
s.mu.Unlock()
57+
}
58+
59+
// MeasureSince adds a measurement for key to the metric store.
60+
func (s *Store) MeasureSince(key string, start time.Time) {
61+
s.mu.Lock()
62+
s.metrics[key] = append(s.metrics[key], time.Since(start))
63+
s.mu.Unlock()
64+
}
65+
66+
// WriteMetrics writes a summary of the metrics to w.
67+
func (s *Store) WriteMetrics(w io.Writer) {
68+
s.mu.Lock()
69+
70+
results := make([]result, len(s.metrics))
71+
72+
var i int
73+
for k, v := range s.metrics {
74+
var sum time.Duration
75+
var max time.Duration
76+
77+
for _, d := range v {
78+
sum += d
79+
if d > max {
80+
max = d
81+
}
82+
}
83+
84+
avg := time.Duration(int(sum) / len(v))
85+
86+
results[i] = result{key: k, count: len(v), max: max, sum: sum, avg: avg}
87+
i++
88+
}
89+
90+
s.mu.Unlock()
91+
92+
// sort and print results
93+
fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "cumulative", "average", "maximum", "", "")
94+
fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "duration", "duration", "duration", "count", "template")
95+
fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "----------", "--------", "--------", "-----", "--------")
96+
97+
sort.Sort(bySum(results))
98+
for _, v := range results {
99+
fmt.Fprintf(w, " %13s %12s %12s %5d %s\n", v.sum, v.avg, v.max, v.count, v.key)
100+
}
101+
102+
}
103+
104+
// A result represents the calculated results for a given metric.
105+
type result struct {
106+
key string
107+
count int
108+
sum time.Duration
109+
max time.Duration
110+
avg time.Duration
111+
}
112+
113+
type bySum []result
114+
115+
func (b bySum) Len() int { return len(b) }
116+
func (b bySum) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
117+
func (b bySum) Less(i, j int) bool { return b[i].sum < b[j].sum }

‎tpl/template.go‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ package tpl
1515

1616
import (
1717
"io"
18+
"time"
1819

1920
"text/template/parse"
2021

2122
"html/template"
2223
texttemplate "text/template"
2324

2425
bp "github.com/gohugoio/hugo/bufferpool"
26+
"github.com/gohugoio/hugo/metrics"
2527
)
2628

2729
var (
@@ -66,13 +68,16 @@ type TemplateDebugger interface {
6668
// TemplateAdapter implements the TemplateExecutor interface.
6769
type TemplateAdapter struct {
6870
Template
71+
Metrics metrics.Provider
6972
}
7073

7174
// Execute executes the current template. The actual execution is performed
7275
// by the embedded text or html template, but we add an implementation here so
7376
// we can add a timer for some metrics.
7477
func (t *TemplateAdapter) Execute(w io.Writer, data interface{}) error {
75-
// TODO(moorereason) metrics fmt.Println("Execute:", t.Name())
78+
if t.Metrics != nil {
79+
defer t.Metrics.MeasureSince(t.Name(), time.Now())
80+
}
7681
return t.Template.Execute(w, data)
7782
}
7883

‎tpl/tplimpl/template.go‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,25 @@ func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
112112
// in the text template collection.
113113
// The templates are stored without the prefix identificator.
114114
name = strings.TrimPrefix(name, textTmplNamePrefix)
115-
return t.text.Lookup(name)
115+
116+
te := t.text.Lookup(name)
117+
if te != nil {
118+
te.Metrics = t.Deps.Metrics
119+
}
120+
return te
116121
}
117122

118123
// Look in both
119124
if te := t.html.Lookup(name); te != nil {
125+
te.Metrics = t.Deps.Metrics
120126
return te
121127
}
122128

123-
return t.text.Lookup(name)
129+
te := t.text.Lookup(name)
130+
if te != nil {
131+
te.Metrics = t.Deps.Metrics
132+
}
133+
return te
124134
}
125135

126136
func (t *templateHandler) clone(d *deps.Deps) *templateHandler {

0 commit comments

Comments
 (0)