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  }