github.com/GuanceCloud/cliutils@v1.1.21/point/equal.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 package point 7 8 import ( 9 "crypto/md5" //nolint:gosec 10 "crypto/sha256" 11 "fmt" 12 "sort" 13 ) 14 15 type EqualOption func(*eqopt) 16 17 type eqopt struct { 18 withMeasurement bool 19 excludeKeys []string 20 } 21 22 func (o *eqopt) keyExlcuded(key string) bool { 23 for _, k := range o.excludeKeys { 24 if k == key { 25 return true 26 } 27 } 28 29 return false 30 } 31 32 func (o *eqopt) kvsEq(l, r KVs) (bool, string) { 33 if len(o.excludeKeys) == 0 && len(l) != len(r) { 34 return false, fmt.Sprintf("count not equal(%d <> %d)", len(l), len(r)) 35 } 36 37 for _, f := range l { 38 if o.keyExlcuded(f.Key) { 39 continue 40 } 41 42 if !r.Has(f.Key) { // key not exists 43 return false, fmt.Sprintf("%s not exists", f.Key) 44 } 45 46 v := r.Get(f.Key) 47 if f.String() != v.String() { // compare proto-string format value 48 return false, fmt.Sprintf("%q value not deep equal(%s <> %s)", f.Key, f, v) 49 } 50 } 51 return true, "" 52 } 53 54 // EqualWithMeasurement set compare on points with/without measurement. 55 func EqualWithMeasurement(on bool) EqualOption { 56 return func(o *eqopt) { o.withMeasurement = on } 57 } 58 59 // EqualWithoutKeys set compare on points without specific keys. 60 func EqualWithoutKeys(keys ...string) EqualOption { 61 return func(o *eqopt) { o.excludeKeys = append(o.excludeKeys, keys...) } 62 } 63 64 // Equal test if two point are the same. 65 // Equality test NOT check on warns and debugs. 66 // If two points equal, they have the same ID(MD5/Sha256), 67 // but same ID do not means they are equal. 68 func (p *Point) Equal(x *Point, opts ...EqualOption) bool { 69 eq, _ := p.EqualWithReason(x, opts...) 70 return eq 71 } 72 73 func (p *Point) EqualWithReason(x *Point, opts ...EqualOption) (bool, string) { 74 if x == nil { 75 return false, "empty point" 76 } 77 78 eopt := &eqopt{withMeasurement: true} 79 for _, opt := range opts { 80 if opt != nil { 81 opt(eopt) 82 } 83 } 84 85 pname := p.Name() //nolint:ifshort 86 ptags := p.Tags() 87 pfields := p.Fields() 88 89 xtags := x.Tags() 90 xfields := x.Fields() 91 92 if !eopt.keyExlcuded("time") { 93 if xtime, ptime := x.Time().UnixNano(), p.Time().UnixNano(); xtime != ptime { 94 return false, fmt.Sprintf("timestamp not equal(%d <> %d)", ptime, xtime) 95 } 96 } 97 98 if eopt.withMeasurement { 99 if xname := x.Name(); eopt.withMeasurement && xname != pname { 100 return false, fmt.Sprintf("measurement not equla(%s <> %s)", pname, xname) 101 } 102 } 103 104 if len(eopt.excludeKeys) == 0 && len(xtags) != len(ptags) { 105 return false, fmt.Sprintf("tag count not equal(%d <> %d)", len(ptags), len(xtags)) 106 } 107 108 if eq, reason := eopt.kvsEq(pfields, xfields); !eq { 109 return eq, fmt.Sprintf("field: %s", reason) 110 } 111 112 if eq, reason := eopt.kvsEq(ptags, xtags); !eq { 113 return eq, fmt.Sprintf("tag: %s", reason) 114 } 115 116 return true, "" 117 } 118 119 // MD5 get point MD5 id. 120 func (p *Point) MD5() string { 121 x := p.hashstr() 122 return fmt.Sprintf("%x", md5.Sum(x)) //nolint:gosec 123 } 124 125 // Sha256 get point Sha256 id. 126 func (p *Point) Sha256() string { 127 x := p.hashstr() 128 h := sha256.New() 129 h.Write(x) 130 return fmt.Sprintf("%x", h.Sum(nil)) 131 } 132 133 // hashstr only count measurement/tag-keys/tag-values as hash string, 134 // other fields(fields/time/debugs/warns ignored). 135 func (p *Point) hashstr() []byte { 136 tags := p.Tags() 137 138 var data []byte 139 140 data = append(data, []byte(p.Name())...) 141 142 sort.Sort(tags) 143 144 for _, t := range tags { 145 data = append(data, []byte(t.Key)...) 146 data = append(data, []byte(t.GetS())...) 147 } 148 return data 149 } 150 151 func (p *Point) TimeSeriesHash() []string { 152 fields := p.Fields() 153 ts := make([]string, len(fields)) 154 hash := p.hashstr() 155 156 for idx, f := range fields { 157 hash := append(hash, []byte(f.Key)...) 158 ts[idx] = fmt.Sprintf("%x", md5.Sum(hash)) //nolint:gosec 159 } 160 161 return ts 162 }