New parser and comparison

This commit is contained in:
Alex Dadgar 2018-10-12 15:25:34 -07:00
parent 364180b396
commit cbb5f21112
5 changed files with 886 additions and 50 deletions

View File

@ -77,6 +77,11 @@ func TimeToPtr(t time.Duration) *time.Duration {
return &t return &t
} }
// Float64ToPtr returns the pointer to an float64
func Float64ToPtr(f float64) *float64 {
return &f
}
func IntMin(a, b int) int { func IntMin(a, b int) int {
if a < b { if a < b {
return a return a

View File

@ -2,8 +2,18 @@ package structs
import ( import (
"fmt" "fmt"
"regexp" "math/big"
"strconv" "strconv"
"strings"
"unicode"
"github.com/hashicorp/nomad/helper"
)
const (
// floatPrecision is the precision used before rounding. It is set to a high
// number to give a high chance of correctly returning equality.
floatPrecision = uint(256)
) )
// BaseUnit is a unique base unit. All units that share the same base unit // BaseUnit is a unique base unit. All units that share the same base unit
@ -27,7 +37,7 @@ type Unit struct {
Base BaseUnit Base BaseUnit
// Multiplier is the multiplier over the base unit (KiB multiplier is 1024) // Multiplier is the multiplier over the base unit (KiB multiplier is 1024)
Multiplier uint64 Multiplier int64
// InverseMultiplier specifies that the multiplier is an inverse so: // InverseMultiplier specifies that the multiplier is an inverse so:
// Base / Multiplier. For example a mW is a W/1000. // Base / Multiplier. For example a mW is a W/1000.
@ -47,83 +57,323 @@ func (u *Unit) Comparable(o *Unit) bool {
// specifying units // specifying units
type Attribute struct { type Attribute struct {
// Float is the float value for the attribute // Float is the float value for the attribute
Float float64 Float *float64
// Int is the int value for the attribute // Int is the int value for the attribute
Int int64 Int *int64
// String is the string value for the attribute // String is the string value for the attribute
String string String *string
// Bool is the bool value for the attribute // Bool is the bool value for the attribute
Bool bool Bool *bool
// Unit is the optional unit for the set int or float value // Unit is the optional unit for the set int or float value
Unit string Unit string
} }
// GoString returns a string representation of the attribute
func (a *Attribute) GoString() string {
if a == nil {
return "nil attribute"
}
var b strings.Builder
if a.Float != nil {
b.WriteString(fmt.Sprintf("%v", *a.Float))
} else if a.Int != nil {
b.WriteString(fmt.Sprintf("%v", *a.Int))
} else if a.Bool != nil {
b.WriteString(fmt.Sprintf("%v", *a.Bool))
} else if a.String != nil {
b.WriteString(*a.String)
}
if a.Unit != "" {
b.WriteString(a.Unit)
}
return b.String()
}
// Validate checks if the attribute is valid // Validate checks if the attribute is valid
func (a *Attribute) Validate() error { func (a *Attribute) Validate() error {
if a.Unit != "" { if a.Unit != "" {
if _, ok := UnitIndex[a.Unit]; !ok { if _, ok := UnitIndex[a.Unit]; !ok {
return fmt.Errorf("unrecognized unit %q", a.Unit) return fmt.Errorf("unrecognized unit %q", a.Unit)
} }
// Check only int/float set
if a.String != nil || a.Bool != nil {
return fmt.Errorf("unit can not be specified on a boolean or string attribute")
}
}
// Assert only one of the attributes is set
set := 0
if a.Float != nil {
set++
}
if a.Int != nil {
set++
}
if a.String != nil {
set++
}
if a.Bool != nil {
set++
}
if set == 0 {
return fmt.Errorf("no attribute value set")
} else if set > 1 {
return fmt.Errorf("only one attribute value may be set")
} }
return nil return nil
} }
var ( // Compare compares two attributes. If the returned boolean value is false, it
// numericWithUnits matches only if it is a integer or float ending with // means the values are not comparable, either because they are of different
// units. It has two capture groups, one for the numeric value and one for // types (bool versus int) or the units are incompatible for comparison.
// the unit value // The returned int will be 0 if a==b, -1 if a < b, and +1 if a > b for all
numericWithUnits = regexp.MustCompile(`^([-]?(?:[0-9]+|[0-9]+\.[0-9]+|\.[0-9]+))\s*([a-zA-Z]+\/?[a-zA-z]+|[a-zA-Z])$`) // values but bool. For bool it will be 0 if a==b or 1 if a!=b.
) func (a *Attribute) Compare(b *Attribute) (int, bool) {
if !a.Comparable(b) {
return 0, false
}
return a.comparitor()(b)
}
// comparitor returns the comparitor function for the attribute
func (a *Attribute) comparitor() compareFn {
if a.Bool != nil {
return a.boolComparitor
}
if a.String != nil {
return a.stringComparitor
}
if a.Int != nil || a.Float != nil {
return a.numberComparitor
}
return nullComparitor
}
// boolComparitor compares two boolean attributes
func (a *Attribute) boolComparitor(b *Attribute) (int, bool) {
if *a.Bool == *b.Bool {
return 0, true
}
return 1, true
}
// stringComparitor compares two string attributes
func (a *Attribute) stringComparitor(b *Attribute) (int, bool) {
return strings.Compare(*a.String, *b.String), true
}
// numberComparitor compares two number attributes, having either Int or Float
// set.
func (a *Attribute) numberComparitor(b *Attribute) (int, bool) {
// If they are both integers we do perfect precision comparisons
if a.Int != nil && b.Int != nil {
return a.intComparitor(b)
}
// Push both into the float space
af := a.getBigFloat()
bf := b.getBigFloat()
if af == nil || bf == nil {
return 0, false
}
return af.Cmp(bf), true
}
// intComparitor compares two integer attributes.
func (a *Attribute) intComparitor(b *Attribute) (int, bool) {
ai := a.getInt()
bi := b.getInt()
if ai == bi {
return 0, true
} else if ai < bi {
return -1, true
} else {
return 1, true
}
}
// nullComparitor always returns false and is used when no comparison function
// is possible
func nullComparitor(*Attribute) (int, bool) {
return 0, false
}
// compareFn is used to compare two attributes. It returns -1, 0, 1 for ordering
// and a boolean for if the comparison is possible.
type compareFn func(b *Attribute) (int, bool)
// getBigFloat returns a big.Float representation of the attribute, converting
// the value to the base unit if a unit is specified.
func (a *Attribute) getBigFloat() *big.Float {
f := new(big.Float)
f.SetPrec(floatPrecision)
if a.Int != nil {
f.SetInt64(*a.Int)
} else if a.Float != nil {
f.SetFloat64(*a.Float)
} else {
return nil
}
// Get the unit
u := a.getTypedUnit()
// If there is no unit just return the float
if u == nil {
return f
}
// Convert to the base unit
multiplier := new(big.Float)
multiplier.SetPrec(floatPrecision)
multiplier.SetInt64(u.Multiplier)
if u.InverseMultiplier {
base := big.NewFloat(1.0)
base.SetPrec(floatPrecision)
multiplier = multiplier.Quo(base, multiplier)
}
f.Mul(f, multiplier)
return f
}
// getInt returns an int representation of the attribute, converting
// the value to the base unit if a unit is specified.
func (a *Attribute) getInt() int64 {
if a.Int == nil {
return 0
}
i := *a.Int
// Get the unit
u := a.getTypedUnit()
// If there is no unit just return the int
if u == nil {
return i
}
if u.InverseMultiplier {
i /= u.Multiplier
} else {
i *= u.Multiplier
}
return i
}
// Comparable returns whether they are comparable
func (a *Attribute) Comparable(b *Attribute) bool {
if a == nil || b == nil {
return false
}
// First use the units to decide if comparison is possible
aUnit := a.getTypedUnit()
bUnit := b.getTypedUnit()
if aUnit != nil && bUnit != nil {
return aUnit.Comparable(bUnit)
} else if aUnit != nil && bUnit == nil {
return false
} else if aUnit == nil && bUnit != nil {
return false
}
if a.String != nil {
if b.String != nil {
return true
}
return false
}
if a.Bool != nil {
if b.Bool != nil {
return true
}
return false
}
return true
}
// getTypedUnit returns the Unit for the attribute or nil if no unit exists.
func (a *Attribute) getTypedUnit() *Unit {
return UnitIndex[a.Unit]
}
// ParseAttribute takes a string and parses it into an attribute, pulling out
// units if they are specified as a suffix on a number
func ParseAttribute(input string) *Attribute { func ParseAttribute(input string) *Attribute {
ll := len(input)
if ll == 0 {
return &Attribute{String: helper.StringToPtr(input)}
}
// Try to parse as a bool // Try to parse as a bool
b, err := strconv.ParseBool(input) b, err := strconv.ParseBool(input)
if err == nil { if err == nil {
return &Attribute{Bool: b} return &Attribute{Bool: helper.BoolToPtr(b)}
} }
// Try to parse as a number.
// Check if the string is a number ending with potential units // Check if the string is a number ending with potential units
if matches := numericWithUnits.FindStringSubmatch(input); len(matches) == 3 { if unicode.IsLetter(rune(input[ll-1])) {
numeric := matches[1] // Try suffix matching
unit := matches[2] var unit string
for _, u := range lengthSortedUnits {
if strings.HasSuffix(input, u) {
unit = u
break
}
}
// Check if we know about the unit. If we don't we can only treat this // Check if we know about the unit. If we don't we can only treat this
// as a string // as a string
if _, ok := UnitIndex[unit]; !ok { if len(unit) == 0 {
return &Attribute{String: input} return &Attribute{String: helper.StringToPtr(input)}
} }
// Grab the numeric
numeric := strings.TrimSpace(strings.TrimSuffix(input, unit))
// Try to parse as an int // Try to parse as an int
i, err := strconv.ParseInt(numeric, 10, 64) i, err := strconv.ParseInt(numeric, 10, 64)
if err == nil { if err == nil {
return &Attribute{Int: i, Unit: unit} return &Attribute{Int: helper.Int64ToPtr(i), Unit: unit}
} }
// Try to parse as a float // Try to parse as a float
f, err := strconv.ParseFloat(numeric, 64) f, err := strconv.ParseFloat(numeric, 64)
if err == nil { if err == nil {
return &Attribute{Float: f, Unit: unit} return &Attribute{Float: helper.Float64ToPtr(f), Unit: unit}
} }
} }
// Try to parse as an int // Try to parse as an int
i, err := strconv.ParseInt(input, 10, 64) i, err := strconv.ParseInt(input, 10, 64)
if err == nil { if err == nil {
return &Attribute{Int: i} return &Attribute{Int: helper.Int64ToPtr(i)}
} }
// Try to parse as a float // Try to parse as a float
f, err := strconv.ParseFloat(input, 64) f, err := strconv.ParseFloat(input, 64)
if err == nil { if err == nil {
return &Attribute{Float: f} return &Attribute{Float: helper.Float64ToPtr(f)}
} }
return &Attribute{String: input} return &Attribute{String: helper.StringToPtr(input)}
} }

View File

@ -1,11 +1,546 @@
package structs package structs
import ( import (
"fmt"
"testing" "testing"
"github.com/hashicorp/nomad/helper"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestAttribute_Validate(t *testing.T) {
cases := []struct {
Input *Attribute
Fail bool
}{
{
Input: &Attribute{
Bool: helper.BoolToPtr(true),
},
},
{
Input: &Attribute{
String: helper.StringToPtr("foo"),
},
},
{
Input: &Attribute{
Int: helper.Int64ToPtr(123),
},
},
{
Input: &Attribute{
Float: helper.Float64ToPtr(123.2),
},
},
{
Input: &Attribute{
Bool: helper.BoolToPtr(true),
Unit: "MB",
},
Fail: true,
},
{
Input: &Attribute{
String: helper.StringToPtr("foo"),
Unit: "MB",
},
Fail: true,
},
{
Input: &Attribute{
Int: helper.Int64ToPtr(123),
Unit: "lolNO",
},
Fail: true,
},
{
Input: &Attribute{
Float: helper.Float64ToPtr(123.2),
Unit: "lolNO",
},
Fail: true,
},
{
Input: &Attribute{
Int: helper.Int64ToPtr(123),
Float: helper.Float64ToPtr(123.2),
Unit: "mW",
},
Fail: true,
},
}
for _, c := range cases {
t.Run(c.Input.GoString(), func(t *testing.T) {
if err := c.Input.Validate(); err != nil && !c.Fail {
require.NoError(t, err)
}
})
}
}
type compareTestCase struct {
A *Attribute
B *Attribute
Expected int
NotComparable bool
}
func TestAttribute_Compare_Bool(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
Bool: helper.BoolToPtr(true),
},
B: &Attribute{
Bool: helper.BoolToPtr(true),
},
Expected: 0,
},
{
A: &Attribute{
Bool: helper.BoolToPtr(true),
},
B: &Attribute{
Bool: helper.BoolToPtr(false),
},
Expected: 1,
},
{
A: &Attribute{
Bool: helper.BoolToPtr(true),
},
B: &Attribute{
String: helper.StringToPtr("foo"),
},
NotComparable: true,
},
{
A: &Attribute{
Bool: helper.BoolToPtr(true),
},
B: &Attribute{
Int: helper.Int64ToPtr(123),
},
NotComparable: true,
},
{
A: &Attribute{
Bool: helper.BoolToPtr(true),
},
B: &Attribute{
Float: helper.Float64ToPtr(123.2),
},
NotComparable: true,
},
}
testComparison(t, cases)
}
func TestAttribute_Compare_String(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
String: helper.StringToPtr("a"),
},
B: &Attribute{
String: helper.StringToPtr("b"),
},
Expected: -1,
},
{
A: &Attribute{
String: helper.StringToPtr("hello"),
},
B: &Attribute{
String: helper.StringToPtr("hello"),
},
Expected: 0,
},
{
A: &Attribute{
String: helper.StringToPtr("b"),
},
B: &Attribute{
String: helper.StringToPtr("a"),
},
Expected: 1,
},
{
A: &Attribute{
String: helper.StringToPtr("hello"),
},
B: &Attribute{
Bool: helper.BoolToPtr(true),
},
NotComparable: true,
},
{
A: &Attribute{
String: helper.StringToPtr("hello"),
},
B: &Attribute{
Int: helper.Int64ToPtr(123),
},
NotComparable: true,
},
{
A: &Attribute{
String: helper.StringToPtr("hello"),
},
B: &Attribute{
Float: helper.Float64ToPtr(123.2),
},
NotComparable: true,
},
}
testComparison(t, cases)
}
func TestAttribute_Compare_Float(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
Float: helper.Float64ToPtr(101.5),
},
B: &Attribute{
Float: helper.Float64ToPtr(100001.5),
},
Expected: -1,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(100001.5),
},
B: &Attribute{
Float: helper.Float64ToPtr(100001.5),
},
Expected: 0,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(999999999.5),
},
B: &Attribute{
Float: helper.Float64ToPtr(101.5),
},
Expected: 1,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(101.5),
},
B: &Attribute{
Bool: helper.BoolToPtr(true),
},
NotComparable: true,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(101.5),
},
B: &Attribute{
String: helper.StringToPtr("hello"),
},
NotComparable: true,
},
}
testComparison(t, cases)
}
func TestAttribute_Compare_Int(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
Int: helper.Int64ToPtr(3),
},
B: &Attribute{
Int: helper.Int64ToPtr(10),
},
Expected: -1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(10),
},
B: &Attribute{
Int: helper.Int64ToPtr(10),
},
Expected: 0,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(100),
},
B: &Attribute{
Int: helper.Int64ToPtr(10),
},
Expected: 1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(10),
},
B: &Attribute{
Bool: helper.BoolToPtr(true),
},
NotComparable: true,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(10),
},
B: &Attribute{
String: helper.StringToPtr("hello"),
},
NotComparable: true,
},
}
testComparison(t, cases)
}
func TestAttribute_Compare_Int_With_Units(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
Int: helper.Int64ToPtr(3),
Unit: "MB",
},
B: &Attribute{
Int: helper.Int64ToPtr(10),
Unit: "MB",
},
Expected: -1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(10),
Unit: "MB",
},
B: &Attribute{
Int: helper.Int64ToPtr(10),
Unit: "MB",
},
Expected: 0,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(100),
Unit: "MB",
},
B: &Attribute{
Int: helper.Int64ToPtr(10),
Unit: "MB",
},
Expected: 1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(3),
Unit: "GB",
},
B: &Attribute{
Int: helper.Int64ToPtr(3),
Unit: "MB",
},
Expected: 1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(1),
Unit: "GiB",
},
B: &Attribute{
Int: helper.Int64ToPtr(1024),
Unit: "MiB",
},
Expected: 0,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(1),
Unit: "GiB",
},
B: &Attribute{
Int: helper.Int64ToPtr(1025),
Unit: "MiB",
},
Expected: -1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(1000),
Unit: "mW",
},
B: &Attribute{
Int: helper.Int64ToPtr(1),
Unit: "W",
},
Expected: 0,
},
}
testComparison(t, cases)
}
func TestAttribute_Compare_Float_With_Units(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
Float: helper.Float64ToPtr(3.0),
Unit: "MB",
},
B: &Attribute{
Float: helper.Float64ToPtr(10.0),
Unit: "MB",
},
Expected: -1,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(10.0),
Unit: "MB",
},
B: &Attribute{
Float: helper.Float64ToPtr(10.0),
Unit: "MB",
},
Expected: 0,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(100.0),
Unit: "MB",
},
B: &Attribute{
Float: helper.Float64ToPtr(10.0),
Unit: "MB",
},
Expected: 1,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(3.0),
Unit: "GB",
},
B: &Attribute{
Float: helper.Float64ToPtr(3.0),
Unit: "MB",
},
Expected: 1,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(1.0),
Unit: "GiB",
},
B: &Attribute{
Float: helper.Float64ToPtr(1024.0),
Unit: "MiB",
},
Expected: 0,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(1.0),
Unit: "GiB",
},
B: &Attribute{
Float: helper.Float64ToPtr(1025.0),
Unit: "MiB",
},
Expected: -1,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(1000.0),
Unit: "mW",
},
B: &Attribute{
Float: helper.Float64ToPtr(1.0),
Unit: "W",
},
Expected: 0,
},
{
A: &Attribute{
Float: helper.Float64ToPtr(1.5),
Unit: "GiB",
},
B: &Attribute{
Float: helper.Float64ToPtr(1400.0),
Unit: "MiB",
},
Expected: 1,
},
}
testComparison(t, cases)
}
func TestAttribute_Compare_IntToFloat(t *testing.T) {
cases := []*compareTestCase{
{
A: &Attribute{
Int: helper.Int64ToPtr(3),
},
B: &Attribute{
Float: helper.Float64ToPtr(10.0),
},
Expected: -1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(10),
},
B: &Attribute{
Float: helper.Float64ToPtr(10.0),
},
Expected: 0,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(10),
},
B: &Attribute{
Float: helper.Float64ToPtr(10.1),
},
Expected: -1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(100),
},
B: &Attribute{
Float: helper.Float64ToPtr(10.0),
},
Expected: 1,
},
{
A: &Attribute{
Int: helper.Int64ToPtr(100),
},
B: &Attribute{
Float: helper.Float64ToPtr(100.00001),
},
Expected: -1,
},
}
testComparison(t, cases)
}
func testComparison(t *testing.T, cases []*compareTestCase) {
for _, c := range cases {
t.Run(fmt.Sprintf("%#v vs %#v", c.A, c.B), func(t *testing.T) {
v, ok := c.A.Compare(c.B)
if !ok && !c.NotComparable {
t.Fatal("should be comparable")
} else if ok {
require.Equal(t, c.Expected, v)
}
})
}
}
func TestAttribute_ParseAndValidate(t *testing.T) { func TestAttribute_ParseAndValidate(t *testing.T) {
cases := []struct { cases := []struct {
Input string Input string
@ -14,102 +549,102 @@ func TestAttribute_ParseAndValidate(t *testing.T) {
{ {
Input: "true", Input: "true",
Expected: &Attribute{ Expected: &Attribute{
Bool: true, Bool: helper.BoolToPtr(true),
}, },
}, },
{ {
Input: "false", Input: "false",
Expected: &Attribute{ Expected: &Attribute{
Bool: false, Bool: helper.BoolToPtr(false),
}, },
}, },
{ {
Input: "100", Input: "100",
Expected: &Attribute{ Expected: &Attribute{
Int: 100, Int: helper.Int64ToPtr(100),
}, },
}, },
{ {
Input: "-100", Input: "-100",
Expected: &Attribute{ Expected: &Attribute{
Int: -100, Int: helper.Int64ToPtr(-100),
}, },
}, },
{ {
Input: "-1.0", Input: "-1.0",
Expected: &Attribute{ Expected: &Attribute{
Float: -1.0, Float: helper.Float64ToPtr(-1.0),
}, },
}, },
{ {
Input: "-100.25", Input: "-100.25",
Expected: &Attribute{ Expected: &Attribute{
Float: -100.25, Float: helper.Float64ToPtr(-100.25),
}, },
}, },
{ {
Input: "1.01", Input: "1.01",
Expected: &Attribute{ Expected: &Attribute{
Float: 1.01, Float: helper.Float64ToPtr(1.01),
}, },
}, },
{ {
Input: "100.25", Input: "100.25",
Expected: &Attribute{ Expected: &Attribute{
Float: 100.25, Float: helper.Float64ToPtr(100.25),
}, },
}, },
{ {
Input: "foobar", Input: "foobar",
Expected: &Attribute{ Expected: &Attribute{
String: "foobar", String: helper.StringToPtr("foobar"),
}, },
}, },
{ {
Input: "foo123bar", Input: "foo123bar",
Expected: &Attribute{ Expected: &Attribute{
String: "foo123bar", String: helper.StringToPtr("foo123bar"),
}, },
}, },
{ {
Input: "100MB", Input: "100MB",
Expected: &Attribute{ Expected: &Attribute{
Int: 100, Int: helper.Int64ToPtr(100),
Unit: "MB", Unit: "MB",
}, },
}, },
{ {
Input: "-100MHz", Input: "-100MHz",
Expected: &Attribute{ Expected: &Attribute{
Int: -100, Int: helper.Int64ToPtr(-100),
Unit: "MHz", Unit: "MHz",
}, },
}, },
{ {
Input: "-1.0MB/s", Input: "-1.0MB/s",
Expected: &Attribute{ Expected: &Attribute{
Float: -1.0, Float: helper.Float64ToPtr(-1.0),
Unit: "MB/s", Unit: "MB/s",
}, },
}, },
{ {
Input: "-100.25GiB/s", Input: "-100.25GiB/s",
Expected: &Attribute{ Expected: &Attribute{
Float: -100.25, Float: helper.Float64ToPtr(-100.25),
Unit: "GiB/s", Unit: "GiB/s",
}, },
}, },
{ {
Input: "1.01TB", Input: "1.01TB",
Expected: &Attribute{ Expected: &Attribute{
Float: 1.01, Float: helper.Float64ToPtr(1.01),
Unit: "TB", Unit: "TB",
}, },
}, },
{ {
Input: "100.25mW", Input: "100.25mW",
Expected: &Attribute{ Expected: &Attribute{
Float: 100.25, Float: helper.Float64ToPtr(100.25),
Unit: "mW", Unit: "mW",
}, },
}, },
@ -123,3 +658,31 @@ func TestAttribute_ParseAndValidate(t *testing.T) {
}) })
} }
} }
func BenchmarkParse(b *testing.B) {
cases := []string{
"true",
"false",
"100",
"-100",
"-1.0",
"-100.25",
"1.01",
"100.25",
"foobar",
"foo123bar",
"100MB",
"-100MHz",
"-1.0MB/s",
"-100.25GiB/s",
"1.01TB",
"100.25mW",
}
// run the Fib function b.N times
for n := 0; n < b.N; n++ {
for _, c := range cases {
ParseAttribute(c)
}
}
}

View File

@ -1,7 +1,17 @@
package structs. package structs
import "sort"
var ( var (
UnitIndex = make(map[string]*Unit, len(binarySIBytes)+len(decimalSIBytes)+len(binarySIByteRates)+len(decimalSIByteRates)+len(watts)+len(hertz)) // numUnits is the number of known units
numUnits = len(binarySIBytes) + len(decimalSIBytes) + len(binarySIByteRates) + len(decimalSIByteRates) + len(watts) + len(hertz)
// UnitIndex is a map of unit name to unit
UnitIndex = make(map[string]*Unit, numUnits)
// lengthSortedUnits is a list of unit names sorted by length with longest
// first
lengthSortedUnits = make([]string, 0, numUnits)
binarySIBytes = []*Unit{ binarySIBytes = []*Unit{
&Unit{ &Unit{
@ -193,6 +203,11 @@ func init() {
for _, units := range [][]*Unit{binarySIBytes, decimalSIBytes, binarySIByteRates, decimalSIByteRates, watts, hertz} { for _, units := range [][]*Unit{binarySIBytes, decimalSIBytes, binarySIByteRates, decimalSIByteRates, watts, hertz} {
for _, unit := range units { for _, unit := range units {
UnitIndex[unit.Name] = unit UnitIndex[unit.Name] = unit
lengthSortedUnits = append(lengthSortedUnits, unit.Name)
} }
} }
sort.Slice(lengthSortedUnits, func(i, j int) bool {
return len(lengthSortedUnits[i]) >= len(lengthSortedUnits[j])
})
} }

View File

@ -1,6 +1,9 @@
package structs package structs
import "github.com/hashicorp/nomad/plugins/shared/structs/proto" import (
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/plugins/shared/structs/proto"
)
func ConvertProtoAttribute(in *proto.Attribute) *Attribute { func ConvertProtoAttribute(in *proto.Attribute) *Attribute {
out := &Attribute{ out := &Attribute{
@ -9,21 +12,21 @@ func ConvertProtoAttribute(in *proto.Attribute) *Attribute {
switch in.Value.(type) { switch in.Value.(type) {
case *proto.Attribute_BoolVal: case *proto.Attribute_BoolVal:
out.Bool = in.GetBoolVal() out.Bool = helper.BoolToPtr(in.GetBoolVal())
case *proto.Attribute_FloatVal: case *proto.Attribute_FloatVal:
out.Float = in.GetFloatVal() out.Float = helper.Float64ToPtr(in.GetFloatVal())
case *proto.Attribute_IntVal: case *proto.Attribute_IntVal:
out.Int = in.GetIntVal() out.Int = helper.Int64ToPtr(in.GetIntVal())
case *proto.Attribute_StringVal: case *proto.Attribute_StringVal:
out.String = in.GetStringVal() out.String = helper.StringToPtr(in.GetStringVal())
default: default:
} }
return out return out
} }
func Pow(a, b uint64) uint64 { func Pow(a, b int64) int64 {
var p uint64 = 1 var p int64 = 1
for b > 0 { for b > 0 {
if b&1 != 0 { if b&1 != 0 {
p *= a p *= a