Skip to content

Commit

Permalink
feat: allow user to convert datatype if valid (#585)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?

- When datatype is specified, attempt to convert span data to given data
type. #541

## Short description of the changes

- This change allows a data check to the rules condition to see if input
span/trace data matches the type specified on the data condition. If it
is not, then attempt to convert the data to specified type (e.g. int to
float, string to int, etc). If it cannot be converted, then do not
convert the data.

---------

Co-authored-by: Faith Chikwekwe <faithchikwekwe@honeycomb.io>
  • Loading branch information
fchikwekwe and fchikwekwe authored Jan 31, 2023
1 parent 17a2f39 commit 809cd9f
Show file tree
Hide file tree
Showing 3 changed files with 865 additions and 6 deletions.
333 changes: 333 additions & 0 deletions config/sampler_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"fmt"
"strconv"
"strings"
)

type DeterministicSamplerConfig struct {
Expand Down Expand Up @@ -45,12 +47,343 @@ type RulesBasedSamplerCondition struct {
Field string
Operator string
Value interface{}
Datatype string
Matches func(value any, exists bool) bool
}

func (r *RulesBasedSamplerCondition) Init() error {
return r.setMatchesFunction()
}

func (r *RulesBasedSamplerCondition) String() string {
return fmt.Sprintf("%+v", *r)
}

func (r *RulesBasedSamplerCondition) setMatchesFunction() error {
switch r.Operator {
case "exists":
r.Matches = func(value any, exists bool) bool {
return exists
}
return nil
case "not-exists":
r.Matches = func(value any, exists bool) bool {
return !exists
}
return nil
case "!=", "=", ">", "<", "<=", ">=":
return setCompareOperators(r, r.Operator)
case "starts-with", "contains", "does-not-contain":
err := setMatchStringBasedOperators(r, r.Operator)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown operator '%s'", r.Operator)
}
return nil
}

func tryConvertToInt(v any) (int, bool) {
switch value := v.(type) {
case int:
return value, true
case int64:
return int(value), true
case float64:
return int(value), true
case bool:
return 0, false
case string:
n, err := strconv.Atoi(value)
if err == nil {
return n, true
}
return 0, false
default:
return 0, false
}
}

func tryConvertToFloat(v any) (float64, bool) {
switch value := v.(type) {
case float64:
return value, true
case int:
return float64(value), true
case int64:
return float64(value), true
case bool:
return 0, false
case string:
n, err := strconv.ParseFloat(value, 64)
return n, err == nil
default:
return 0, false
}
}

func tryConvertToString(v any) (string, bool) {
switch value := v.(type) {
case string:
return value, true
case int:
return strconv.Itoa(value), true
case int64:
return strconv.FormatInt(value, 10), true
case float64:
return strconv.FormatFloat(value, 'E', -1, 64), false
case bool:
return strconv.FormatBool(value), true
default:
return "", false
}
}

func tryConvertToBool(v any) bool {
value, ok := tryConvertToString(v)
if !ok {
return false
}
str, err := strconv.ParseBool(value)
if err != nil {
return false
}
if str {
return true
} else {
return false
}
}

func setCompareOperators(r *RulesBasedSamplerCondition, condition string) error {
switch r.Datatype {
case "string":
conditionValue, ok := tryConvertToString(r.Value)
if !ok {
return fmt.Errorf("could not convert %v to string", r.Value)
}

// check if conditionValue and spanValue are not equal
switch condition {
case "!=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToString(spanValue); exists && ok {
return n != conditionValue
}
return false
}
return nil
case "=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToString(spanValue); exists && ok {
return n == conditionValue
}
return false
}
return nil
case ">":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToString(spanValue); exists && ok {
return n > conditionValue
}
return false
}
return nil
case "<":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToString(spanValue); exists && ok {
return n < conditionValue
}
return false
}
return nil
case "<=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToString(spanValue); exists && ok {
return n <= conditionValue
}
return false
}
return nil
}
case "int":
// check if conditionValue and spanValue are not equal
conditionValue, ok := tryConvertToInt(r.Value)
if !ok {
return fmt.Errorf("could not convert %v to string", r.Value)
}
switch condition {
case "!=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToInt(spanValue); exists && ok {
return n != conditionValue
}
return false
}
return nil
case "=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToInt(spanValue); exists && ok {
return n == conditionValue
}
return false
}
return nil
case ">":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToInt(spanValue); exists && ok {
return n > conditionValue
}
return false
}
return nil
case ">=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToInt(spanValue); exists && ok {
return n >= conditionValue
}
return false
}
return nil
case "<":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToInt(spanValue); exists && ok {
return n < conditionValue
}
return false
}
return nil
case "<=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToInt(spanValue); exists && ok {
return n <= conditionValue
}
return false
}
return nil
}
case "float":
conditionValue, ok := tryConvertToFloat(r.Value)
if !ok {
return fmt.Errorf("could not convert %v to string", r.Value)
}
// check if conditionValue and spanValue are not equal
switch condition {
case "!=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToFloat(spanValue); exists && ok {
return n != conditionValue
}
return false
}
return nil
case "=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToFloat(spanValue); exists && ok {
return n == conditionValue
}
return false
}
return nil
case ">":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToFloat(spanValue); exists && ok {
return n > conditionValue
}
return false
}
return nil
case ">=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToFloat(spanValue); exists && ok {
return n >= conditionValue
}
return false
}
return nil
case "<":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToFloat(spanValue); exists && ok {
return n < conditionValue
}
return false
}
return nil
case "<=":
r.Matches = func(spanValue any, exists bool) bool {
if n, ok := tryConvertToFloat(spanValue); exists && ok {
return n <= conditionValue
}
return false
}
return nil
}
case "bool":
conditionValue := tryConvertToBool(r.Value)

switch condition {
case "!=":
r.Matches = func(spanValue any, exists bool) bool {
if n := tryConvertToBool(spanValue); exists && n {
return n != conditionValue
}
return false
}
return nil
case "=":
r.Matches = func(spanValue any, exists bool) bool {
if n := tryConvertToBool(spanValue); exists && n {
return n == conditionValue
}
return false
}
return nil
}
case "":
// user did not specify dataype, so do not specify matches function
default:
return fmt.Errorf("%s must be either string, int, float or bool", r.Datatype)
}
return nil
}

func setMatchStringBasedOperators(r *RulesBasedSamplerCondition, condition string) error {
conditionValue, ok := tryConvertToString(r.Value)
if !ok {
return fmt.Errorf("%s value must be a string, but was '%s'", condition, r.Value)
}

switch condition {
case "starts-with":
r.Matches = func(spanValue any, exists bool) bool {
s, ok := spanValue.(string)
if ok {
return strings.HasPrefix(s, conditionValue)
}
return false
}
case "contains":
r.Matches = func(spanValue any, exists bool) bool {
s, ok := spanValue.(string)
if ok {
return strings.Contains(s, conditionValue)
}
return false
}
case "does-not-contain":
r.Matches = func(spanValue any, exists bool) bool {
s, ok := spanValue.(string)
if ok {
return !strings.Contains(s, conditionValue)
}
return false
}
}

return nil
}

type RulesBasedDownstreamSampler struct {
DynamicSampler *DynamicSamplerConfig
EMADynamicSampler *EMADynamicSamplerConfig
Expand Down
Loading

0 comments on commit 809cd9f

Please sign in to comment.