metrics: Fix --templateMetricsHints

Also improve non-string comparisons.

Fixes #7048
This commit is contained in:
Bjørn Erik Pedersen 2020-03-12 17:09:49 +01:00
parent 18cb21ff2e
commit 5eadc4c0a8
3 changed files with 85 additions and 10 deletions

View file

@ -13,7 +13,11 @@
package types package types
import "github.com/spf13/cast" import (
"html/template"
"github.com/spf13/cast"
)
// ToStringSlicePreserveString converts v to a string slice. // ToStringSlicePreserveString converts v to a string slice.
// If v is a string, it will be wrapped in a string slice. // If v is a string, it will be wrapped in a string slice.
@ -26,3 +30,39 @@ func ToStringSlicePreserveString(v interface{}) []string {
} }
return cast.ToStringSlice(v) return cast.ToStringSlice(v)
} }
// TypeToString converts v to a string if it's a valid string type.
// Note that this will not try to convert numeric values etc.,
// use ToString for that.
func TypeToString(v interface{}) (string, bool) {
switch s := v.(type) {
case string:
return s, true
case template.HTML:
return string(s), true
case template.CSS:
return string(s), true
case template.HTMLAttr:
return string(s), true
case template.JS:
return string(s), true
case template.JSStr:
return string(s), true
case template.URL:
return string(s), true
case template.Srcset:
return string(s), true
}
return "", false
}
// ToString converts v to a string.
func ToString(v interface{}) string {
if s, ok := TypeToString(v); ok {
return s
}
return cast.ToString(v)
}

View file

@ -15,6 +15,12 @@
package metrics package metrics
import ( import (
"reflect"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/common/types"
"fmt" "fmt"
"io" "io"
"math" "math"
@ -25,8 +31,6 @@ import (
"time" "time"
"github.com/gohugoio/hugo/compare" "github.com/gohugoio/hugo/compare"
"github.com/gohugoio/hugo/common/hreflect"
) )
// The Provider interface defines an interface for measuring metrics. // The Provider interface defines an interface for measuring metrics.
@ -51,15 +55,17 @@ type diff struct {
simSum int simSum int
} }
var counter = 0
func (d *diff) add(v interface{}) *diff { func (d *diff) add(v interface{}) *diff {
if !hreflect.IsTruthful(v) { if types.IsNil(d.baseline) {
d.baseline = v d.baseline = v
d.count = 1 d.count = 1
d.simSum = 100 // If we get only one it is very cache friendly. d.simSum = 100 // If we get only one it is very cache friendly.
return d return d
} }
adder := howSimilar(v, d.baseline)
d.simSum += howSimilar(v, d.baseline) d.simSum += adder
d.count++ d.count++
return d return d
@ -113,6 +119,7 @@ func (s *Store) TrackValue(key string, value interface{}) {
} }
d.add(value) d.add(value)
s.diffmu.Unlock() s.diffmu.Unlock()
} }
@ -135,6 +142,7 @@ func (s *Store) WriteMetrics(w io.Writer) {
var max time.Duration var max time.Duration
diff, found := s.diffs[k] diff, found := s.diffs[k]
cacheFactor := 0 cacheFactor := 0
if found { if found {
cacheFactor = int(math.Floor(float64(diff.simSum) / float64(diff.count))) cacheFactor = int(math.Floor(float64(diff.simSum) / float64(diff.count)))
@ -196,11 +204,19 @@ func (b bySum) Less(i, j int) bool { return b[i].sum > b[j].sum }
// howSimilar is a naive diff implementation that returns // howSimilar is a naive diff implementation that returns
// a number between 0-100 indicating how similar a and b are. // a number between 0-100 indicating how similar a and b are.
func howSimilar(a, b interface{}) int { func howSimilar(a, b interface{}) int {
// TODO(bep) object equality fast path, but remember that t1, t2 := reflect.TypeOf(a), reflect.TypeOf(b)
// we can get anytning in here. if t1 != t2 {
return 0
}
as, ok1 := a.(string) if t1.Comparable() && t2.Comparable() {
bs, ok2 := b.(string) if a == b {
return 100
}
}
as, ok1 := types.TypeToString(a)
bs, ok2 := types.TypeToString(b)
if ok1 && ok2 { if ok1 && ok2 {
return howSimilarStrings(as, bs) return howSimilarStrings(as, bs)
@ -222,13 +238,21 @@ func howSimilar(a, b interface{}) int {
return 90 return 90
} }
h1, h2 := helpers.HashString(a), helpers.HashString(b)
if h1 == h2 {
return 100
}
return 0 return 0
} }
// howSimilar is a naive diff implementation that returns // howSimilar is a naive diff implementation that returns
// a number between 0-100 indicating how similar a and b are. // a number between 0-100 indicating how similar a and b are.
// 100 is when all words in a also exists in b. // 100 is when all words in a also exists in b.
func howSimilarStrings(a, b string) int { func howSimilarStrings(a, b string) int {
if a == b {
return 100
}
// Give some weight to the word positions. // Give some weight to the word positions.
const partitionSize = 4 const partitionSize = 4

View file

@ -14,6 +14,7 @@
package metrics package metrics
import ( import (
"html/template"
"strings" "strings"
"testing" "testing"
@ -39,13 +40,23 @@ func TestSimilarPercentage(t *testing.T) {
c.Assert(howSimilar("The Hugo", "The Hugo Rules"), qt.Equals, 66) c.Assert(howSimilar("The Hugo", "The Hugo Rules"), qt.Equals, 66)
c.Assert(howSimilar("Totally different", "Not Same"), qt.Equals, 0) c.Assert(howSimilar("Totally different", "Not Same"), qt.Equals, 0)
c.Assert(howSimilar(sentence, sentenceReversed), qt.Equals, 14) c.Assert(howSimilar(sentence, sentenceReversed), qt.Equals, 14)
c.Assert(howSimilar(template.HTML("Hugo Rules"), template.HTML("Hugo Rules")), qt.Equals, 100)
c.Assert(howSimilar(map[string]interface{}{"a": 32, "b": 33}, map[string]interface{}{"a": 32, "b": 33}), qt.Equals, 100)
c.Assert(howSimilar(map[string]interface{}{"a": 32, "b": 33}, map[string]interface{}{"a": 32, "b": 34}), qt.Equals, 0)
} }
type testStruct struct {
Name string
}
func TestSimilarPercentageNonString(t *testing.T) { func TestSimilarPercentageNonString(t *testing.T) {
c := qt.New(t) c := qt.New(t)
c.Assert(howSimilar(page.NopPage, page.NopPage), qt.Equals, 100) c.Assert(howSimilar(page.NopPage, page.NopPage), qt.Equals, 100)
c.Assert(howSimilar(page.Pages{}, page.Pages{}), qt.Equals, 90) c.Assert(howSimilar(page.Pages{}, page.Pages{}), qt.Equals, 90)
c.Assert(howSimilar(testStruct{Name: "A"}, testStruct{Name: "B"}), qt.Equals, 0)
c.Assert(howSimilar(testStruct{Name: "A"}, testStruct{Name: "A"}), qt.Equals, 100)
} }
func BenchmarkHowSimilar(b *testing.B) { func BenchmarkHowSimilar(b *testing.B) {