Skip to content

Commit

Permalink
internal: promote toFloat64 from ddtrace/tracer (#3237)
Browse files Browse the repository at this point in the history
Signed-off-by: Eliott Bouhana <eliott.bouhana@datadoghq.com>
  • Loading branch information
eliottness authored Feb 28, 2025
1 parent 00951e4 commit e378af5
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 92 deletions.
4 changes: 2 additions & 2 deletions ddtrace/tracer/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (s *span) SetTag(key string, value interface{}) {
s.setMeta(key, v)
return
}
if v, ok := toFloat64(value); ok {
if v, ok := sharedinternal.ToFloat64(value); ok {
s.setMetric(key, v)
return
}
Expand Down Expand Up @@ -192,7 +192,7 @@ func (s *span) SetTag(key string, value interface{}) {
for i := 0; i < slice.Len(); i++ {
key := fmt.Sprintf("%s.%d", key, i)
v := slice.Index(i)
if num, ok := toFloat64(v.Interface()); ok {
if num, ok := sharedinternal.ToFloat64(v.Interface()); ok {
s.setMetric(key, num)
} else {
s.setMeta(key, fmt.Sprintf("%v", v))
Expand Down
47 changes: 0 additions & 47 deletions ddtrace/tracer/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,6 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames"
)

// toFloat64 attempts to convert value into a float64. If the value is an integer
// greater or equal to 2^53 or less than or equal to -2^53, it will not be converted
// into a float64 to avoid losing precision. If it succeeds in converting, toFloat64
// returns the value and true, otherwise 0 and false.
func toFloat64(value interface{}) (f float64, ok bool) {
const max = (int64(1) << 53) - 1
const min = -max
// If any other type is added here, remember to add it to the type switch in
// the `span.SetTag` function to handle pointers to these supported types.
switch i := value.(type) {
case byte:
return float64(i), true
case float32:
return float64(i), true
case float64:
return i, true
case int:
return float64(i), true
case int8:
return float64(i), true
case int16:
return float64(i), true
case int32:
return float64(i), true
case int64:
if i > max || i < min {
return 0, false
}
return float64(i), true
case uint:
return float64(i), true
case uint16:
return float64(i), true
case uint32:
return float64(i), true
case uint64:
if i > uint64(max) {
return 0, false
}
return float64(i), true
case samplernames.SamplerName:
return float64(i), true
default:
return 0, false
}
}

// parseUint64 parses a uint64 from either an unsigned 64 bit base-10 string
// or a signed 64 bit base-10 string representing an unsigned integer
func parseUint64(str string) (uint64, error) {
Expand Down
40 changes: 1 addition & 39 deletions ddtrace/tracer/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,10 @@ import (
"testing"

"github.com/stretchr/testify/assert"

"gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames"
)

func TestToFloat64(t *testing.T) {
for i, tt := range [...]struct {
value interface{}
f float64
ok bool
}{
0: {1, 1, true},
1: {byte(1), 1, true},
2: {int(1), 1, true},
3: {int16(1), 1, true},
4: {int32(1), 1, true},
5: {int64(1), 1, true},
6: {uint(1), 1, true},
7: {uint16(1), 1, true},
8: {uint32(1), 1, true},
9: {uint64(1), 1, true},
10: {"a", 0, false},
11: {float32(1.25), 1.25, true},
12: {float64(1.25), 1.25, true},
13: {intUpperLimit, 0, false},
14: {intUpperLimit + 1, 0, false},
15: {intUpperLimit - 1, float64(intUpperLimit - 1), true},
16: {intLowerLimit, 0, false},
17: {intLowerLimit - 1, 0, false},
18: {intLowerLimit + 1, float64(intLowerLimit + 1), true},
19: {-1024, -1024.0, true},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
f, ok := toFloat64(tt.value)
if ok != tt.ok {
t.Fatalf("expected ok: %t", tt.ok)
}
if f != tt.f {
t.Fatalf("expected: %#v, got: %#v", tt.f, f)
}
})
}
}

func TestParseUint64(t *testing.T) {
t.Run("negative", func(t *testing.T) {
id, err := parseUint64("-8809075535603237910")
Expand Down
48 changes: 48 additions & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sync/atomic"

xsync "github.com/puzpuzpuz/xsync/v3"
"gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames"
)

// OtelTagsDelimeter is the separator between key-val pairs for OTEL env vars
Expand Down Expand Up @@ -102,3 +103,50 @@ func (cm *XSyncMapCounterMap) GetAndReset() map[string]int64 {
})
return ret
}

// ToFloat64 attempts to convert value into a float64. If the value is an integer
// greater or equal to 2^53 or less than or equal to -2^53, it will not be converted
// into a float64 to avoid losing precision. If it succeeds in converting, toFloat64
// returns the value and true, otherwise 0 and false.
func ToFloat64(value any) (f float64, ok bool) {
const maxFloat = (int64(1) << 53) - 1
const minFloat = -maxFloat
// If any other type is added here, remember to add it to the type switch in
// the `span.SetTag` function to handle pointers to these supported types.
switch i := value.(type) {
case byte:
return float64(i), true
case float32:
return float64(i), true
case float64:
return i, true
case int:
return float64(i), true
case int8:
return float64(i), true
case int16:
return float64(i), true
case int32:
return float64(i), true
case int64:
if i > maxFloat || i < minFloat {
return 0, false
}
return float64(i), true
case uint:
return float64(i), true
case uint16:
return float64(i), true
case uint32:
return float64(i), true
case uint64:
if i > uint64(maxFloat) {
return 0, false
}
return float64(i), true
case samplernames.SamplerName:
return float64(i), true
default:
return 0, false
}
}
49 changes: 45 additions & 4 deletions internal/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.

package internal

import (
"context"
"fmt"
"math/rand"
"strconv"
"strings"
Expand All @@ -24,7 +24,6 @@ func BenchmarkIter(b *testing.B) {
m.Iter(func(key string, val string) {})
}
}

func TestLockMapThrash(t *testing.T) {
wg := sync.WaitGroup{}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
Expand Down Expand Up @@ -65,7 +64,6 @@ func TestLockMapThrash(t *testing.T) {
wg.Wait()
assert.Equal(t, len(lm.m), int(lm.c))
}

func TestXSyncMapCounterMap(t *testing.T) {
t.Run("basic", func(t *testing.T) {
assert := assert.New(t)
Expand Down Expand Up @@ -102,7 +100,6 @@ func TestXSyncMapCounterMap(t *testing.T) {
assert.Equal(map[string]int64{"key": 10}, cm.GetAndReset())
})
}

func BenchmarkXSyncMapCounterMap(b *testing.B) {
b.Run("base_case", func(b *testing.B) {
b.ReportAllocs()
Expand Down Expand Up @@ -165,3 +162,47 @@ func BenchmarkXSyncMapCounterMap(b *testing.B) {
assert.Equal(b, map[string]int64{"key": int64(b.N)}, cm.GetAndReset())
})
}

func TestToFloat64(t *testing.T) {
const (
intUpperLimit = int64(1) << 53
intLowerLimit = -intUpperLimit
)

for i, tt := range [...]struct {
value interface{}
f float64
ok bool
}{
0: {1, 1, true},
1: {byte(1), 1, true},
2: {int(1), 1, true},
3: {int16(1), 1, true},
4: {int32(1), 1, true},
5: {int64(1), 1, true},
6: {uint(1), 1, true},
7: {uint16(1), 1, true},
8: {uint32(1), 1, true},
9: {uint64(1), 1, true},
10: {"a", 0, false},
11: {float32(1.25), 1.25, true},
12: {float64(1.25), 1.25, true},
13: {intUpperLimit, 0, false},
14: {intUpperLimit + 1, 0, false},
15: {intUpperLimit - 1, float64(intUpperLimit - 1), true},
16: {intLowerLimit, 0, false},
17: {intLowerLimit - 1, 0, false},
18: {intLowerLimit + 1, float64(intLowerLimit + 1), true},
19: {-1024, -1024.0, true},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
f, ok := ToFloat64(tt.value)
if ok != tt.ok {
t.Fatalf("expected ok: %t", tt.ok)
}
if f != tt.f {
t.Fatalf("expected: %#v, got: %#v", tt.f, f)
}
})
}
}

0 comments on commit e378af5

Please sign in to comment.