From 5eadc4c0a8e5c51e72670591c4b7877e79c15e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 12 Mar 2020 17:09:49 +0100 Subject: [PATCH] metrics: Fix --templateMetricsHints Also improve non-string comparisons. Fixes #7048 --- common/types/convert.go | 42 ++++++++++++++++++++++++++++++++++++++++- metrics/metrics.go | 42 ++++++++++++++++++++++++++++++++--------- metrics/metrics_test.go | 11 +++++++++++ 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/common/types/convert.go b/common/types/convert.go index b55330757..24e01c273 100644 --- a/common/types/convert.go +++ b/common/types/convert.go @@ -13,7 +13,11 @@ package types -import "github.com/spf13/cast" +import ( + "html/template" + + "github.com/spf13/cast" +) // ToStringSlicePreserveString converts v to 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) } + +// 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) + +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 329981202..30a69be4b 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -15,6 +15,12 @@ package metrics import ( + "reflect" + + "github.com/gohugoio/hugo/helpers" + + "github.com/gohugoio/hugo/common/types" + "fmt" "io" "math" @@ -25,8 +31,6 @@ import ( "time" "github.com/gohugoio/hugo/compare" - - "github.com/gohugoio/hugo/common/hreflect" ) // The Provider interface defines an interface for measuring metrics. @@ -51,15 +55,17 @@ type diff struct { simSum int } +var counter = 0 + func (d *diff) add(v interface{}) *diff { - if !hreflect.IsTruthful(v) { + if types.IsNil(d.baseline) { d.baseline = v d.count = 1 d.simSum = 100 // If we get only one it is very cache friendly. return d } - - d.simSum += howSimilar(v, d.baseline) + adder := howSimilar(v, d.baseline) + d.simSum += adder d.count++ return d @@ -113,6 +119,7 @@ func (s *Store) TrackValue(key string, value interface{}) { } d.add(value) + s.diffmu.Unlock() } @@ -135,6 +142,7 @@ func (s *Store) WriteMetrics(w io.Writer) { var max time.Duration diff, found := s.diffs[k] + cacheFactor := 0 if found { 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 // a number between 0-100 indicating how similar a and b are. func howSimilar(a, b interface{}) int { - // TODO(bep) object equality fast path, but remember that - // we can get anytning in here. + t1, t2 := reflect.TypeOf(a), reflect.TypeOf(b) + if t1 != t2 { + return 0 + } - as, ok1 := a.(string) - bs, ok2 := b.(string) + if t1.Comparable() && t2.Comparable() { + if a == b { + return 100 + } + } + + as, ok1 := types.TypeToString(a) + bs, ok2 := types.TypeToString(b) if ok1 && ok2 { return howSimilarStrings(as, bs) @@ -222,13 +238,21 @@ func howSimilar(a, b interface{}) int { return 90 } + h1, h2 := helpers.HashString(a), helpers.HashString(b) + if h1 == h2 { + return 100 + } return 0 + } // howSimilar is a naive diff implementation that returns // a number between 0-100 indicating how similar a and b are. // 100 is when all words in a also exists in b. func howSimilarStrings(a, b string) int { + if a == b { + return 100 + } // Give some weight to the word positions. const partitionSize = 4 diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index d4c362b7b..057d58662 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -14,6 +14,7 @@ package metrics import ( + "html/template" "strings" "testing" @@ -39,13 +40,23 @@ func TestSimilarPercentage(t *testing.T) { c.Assert(howSimilar("The Hugo", "The Hugo Rules"), qt.Equals, 66) c.Assert(howSimilar("Totally different", "Not Same"), qt.Equals, 0) 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) { c := qt.New(t) c.Assert(howSimilar(page.NopPage, page.NopPage), qt.Equals, 100) 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) {