Merge pull request #4779 from hashicorp/f-attributes-typed

Attribute object that supports units and comparison
This commit is contained in:
Alex Dadgar 2018-10-15 13:16:36 -07:00 committed by GitHub
commit 6b11abaccd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 1597 additions and 0 deletions

View file

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

View file

@ -0,0 +1,379 @@
package structs
import (
"fmt"
"math/big"
"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
// should be comparable.
type BaseUnit uint16
const (
UnitScalar BaseUnit = iota
UnitByte
UnitByteRate
UnitHertz
UnitWatt
)
// Unit describes a unit and its multiplier over the base unit type
type Unit struct {
// Name is the name of the unit (GiB, MB/s)
Name string
// Base is the base unit for the unit
Base BaseUnit
// Multiplier is the multiplier over the base unit (KiB multiplier is 1024)
Multiplier int64
// InverseMultiplier specifies that the multiplier is an inverse so:
// Base / Multiplier. For example a mW is a W/1000.
InverseMultiplier bool
}
// Comparable returns if two units are comparable
func (u *Unit) Comparable(o *Unit) bool {
if u == nil || o == nil {
return false
}
return u.Base == o.Base
}
// Attribute is used to describe the value of an attribute, optionally
// specifying units
type Attribute struct {
// Float is the float value for the attribute
Float *float64
// Int is the int value for the attribute
Int *int64
// String is the string value for the attribute
String *string
// Bool is the bool value for the attribute
Bool *bool
// Unit is the optional unit for the set int or float value
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
func (a *Attribute) Validate() error {
if a.Unit != "" {
if _, ok := UnitIndex[a.Unit]; !ok {
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
}
// Compare compares two attributes. If the returned boolean value is false, it
// means the values are not comparable, either because they are of different
// types (bool versus int) or the units are incompatible for comparison.
// The returned int will be 0 if a==b, -1 if a < b, and +1 if a > b for all
// 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.comparator()(b)
}
// comparator returns the comparator function for the attribute
func (a *Attribute) comparator() compareFn {
if a.Bool != nil {
return a.boolComparator
}
if a.String != nil {
return a.stringComparator
}
if a.Int != nil || a.Float != nil {
return a.numberComparator
}
return nullComparator
}
// boolComparator compares two boolean attributes
func (a *Attribute) boolComparator(b *Attribute) (int, bool) {
if *a.Bool == *b.Bool {
return 0, true
}
return 1, true
}
// stringComparator compares two string attributes
func (a *Attribute) stringComparator(b *Attribute) (int, bool) {
return strings.Compare(*a.String, *b.String), true
}
// numberComparator compares two number attributes, having either Int or Float
// set.
func (a *Attribute) numberComparator(b *Attribute) (int, bool) {
// If they are both integers we do perfect precision comparisons
if a.Int != nil && b.Int != nil {
return a.intComparator(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
}
// intComparator compares two integer attributes.
func (a *Attribute) intComparator(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
}
}
// nullComparator always returns false and is used when no comparison function
// is possible
func nullComparator(*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 {
ll := len(input)
if ll == 0 {
return &Attribute{String: helper.StringToPtr(input)}
}
// Try to parse as a bool
b, err := strconv.ParseBool(input)
if err == nil {
return &Attribute{Bool: helper.BoolToPtr(b)}
}
// Check if the string is a number ending with potential units
if unicode.IsLetter(rune(input[ll-1])) {
// Try suffix matching
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
// as a string
if len(unit) == 0 {
return &Attribute{String: helper.StringToPtr(input)}
}
// Grab the numeric
numeric := strings.TrimSpace(strings.TrimSuffix(input, unit))
// Try to parse as an int
i, err := strconv.ParseInt(numeric, 10, 64)
if err == nil {
return &Attribute{Int: helper.Int64ToPtr(i), Unit: unit}
}
// Try to parse as a float
f, err := strconv.ParseFloat(numeric, 64)
if err == nil {
return &Attribute{Float: helper.Float64ToPtr(f), Unit: unit}
}
}
// Try to parse as an int
i, err := strconv.ParseInt(input, 10, 64)
if err == nil {
return &Attribute{Int: helper.Int64ToPtr(i)}
}
// Try to parse as a float
f, err := strconv.ParseFloat(input, 64)
if err == nil {
return &Attribute{Float: helper.Float64ToPtr(f)}
}
return &Attribute{String: helper.StringToPtr(input)}
}

View file

@ -0,0 +1,687 @@
package structs
import (
"fmt"
"testing"
"github.com/hashicorp/nomad/helper"
"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) {
cases := []struct {
Input string
Expected *Attribute
}{
{
Input: "true",
Expected: &Attribute{
Bool: helper.BoolToPtr(true),
},
},
{
Input: "false",
Expected: &Attribute{
Bool: helper.BoolToPtr(false),
},
},
{
Input: "100",
Expected: &Attribute{
Int: helper.Int64ToPtr(100),
},
},
{
Input: "-100",
Expected: &Attribute{
Int: helper.Int64ToPtr(-100),
},
},
{
Input: "-1.0",
Expected: &Attribute{
Float: helper.Float64ToPtr(-1.0),
},
},
{
Input: "-100.25",
Expected: &Attribute{
Float: helper.Float64ToPtr(-100.25),
},
},
{
Input: "1.01",
Expected: &Attribute{
Float: helper.Float64ToPtr(1.01),
},
},
{
Input: "100.25",
Expected: &Attribute{
Float: helper.Float64ToPtr(100.25),
},
},
{
Input: "foobar",
Expected: &Attribute{
String: helper.StringToPtr("foobar"),
},
},
{
Input: "foo123bar",
Expected: &Attribute{
String: helper.StringToPtr("foo123bar"),
},
},
{
Input: "100MB",
Expected: &Attribute{
Int: helper.Int64ToPtr(100),
Unit: "MB",
},
},
{
Input: "-100MHz",
Expected: &Attribute{
Int: helper.Int64ToPtr(-100),
Unit: "MHz",
},
},
{
Input: "-1.0MB/s",
Expected: &Attribute{
Float: helper.Float64ToPtr(-1.0),
Unit: "MB/s",
},
},
{
Input: "-100.25GiB/s",
Expected: &Attribute{
Float: helper.Float64ToPtr(-100.25),
Unit: "GiB/s",
},
},
{
Input: "1.01TB",
Expected: &Attribute{
Float: helper.Float64ToPtr(1.01),
Unit: "TB",
},
},
{
Input: "100.25mW",
Expected: &Attribute{
Float: helper.Float64ToPtr(100.25),
Unit: "mW",
},
},
}
for _, c := range cases {
t.Run(c.Input, func(t *testing.T) {
a := ParseAttribute(c.Input)
require.Equal(t, c.Expected, a)
require.NoError(t, a.Validate())
})
}
}
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",
}
for n := 0; n < b.N; n++ {
for _, c := range cases {
ParseAttribute(c)
}
}
}

View file

@ -0,0 +1,250 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: attribute.proto
package proto
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// Attribute is used to describe the value of an attribute, optionally
// specifying units
type Attribute struct {
// Types that are valid to be assigned to Value:
// *Attribute_FloatVal
// *Attribute_IntVal
// *Attribute_StringVal
// *Attribute_BoolVal
Value isAttribute_Value `protobuf_oneof:"value"`
// unit gives the unit type: MHz, MB, etc.
Unit string `protobuf:"bytes,5,opt,name=unit,proto3" json:"unit,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Attribute) Reset() { *m = Attribute{} }
func (m *Attribute) String() string { return proto.CompactTextString(m) }
func (*Attribute) ProtoMessage() {}
func (*Attribute) Descriptor() ([]byte, []int) {
return fileDescriptor_attribute_bfac9d16cf08e8f5, []int{0}
}
func (m *Attribute) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Attribute.Unmarshal(m, b)
}
func (m *Attribute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Attribute.Marshal(b, m, deterministic)
}
func (dst *Attribute) XXX_Merge(src proto.Message) {
xxx_messageInfo_Attribute.Merge(dst, src)
}
func (m *Attribute) XXX_Size() int {
return xxx_messageInfo_Attribute.Size(m)
}
func (m *Attribute) XXX_DiscardUnknown() {
xxx_messageInfo_Attribute.DiscardUnknown(m)
}
var xxx_messageInfo_Attribute proto.InternalMessageInfo
type isAttribute_Value interface {
isAttribute_Value()
}
type Attribute_FloatVal struct {
FloatVal float64 `protobuf:"fixed64,1,opt,name=float_val,json=floatVal,proto3,oneof"`
}
type Attribute_IntVal struct {
IntVal int64 `protobuf:"varint,2,opt,name=int_val,json=intVal,proto3,oneof"`
}
type Attribute_StringVal struct {
StringVal string `protobuf:"bytes,3,opt,name=string_val,json=stringVal,proto3,oneof"`
}
type Attribute_BoolVal struct {
BoolVal bool `protobuf:"varint,4,opt,name=bool_val,json=boolVal,proto3,oneof"`
}
func (*Attribute_FloatVal) isAttribute_Value() {}
func (*Attribute_IntVal) isAttribute_Value() {}
func (*Attribute_StringVal) isAttribute_Value() {}
func (*Attribute_BoolVal) isAttribute_Value() {}
func (m *Attribute) GetValue() isAttribute_Value {
if m != nil {
return m.Value
}
return nil
}
func (m *Attribute) GetFloatVal() float64 {
if x, ok := m.GetValue().(*Attribute_FloatVal); ok {
return x.FloatVal
}
return 0
}
func (m *Attribute) GetIntVal() int64 {
if x, ok := m.GetValue().(*Attribute_IntVal); ok {
return x.IntVal
}
return 0
}
func (m *Attribute) GetStringVal() string {
if x, ok := m.GetValue().(*Attribute_StringVal); ok {
return x.StringVal
}
return ""
}
func (m *Attribute) GetBoolVal() bool {
if x, ok := m.GetValue().(*Attribute_BoolVal); ok {
return x.BoolVal
}
return false
}
func (m *Attribute) GetUnit() string {
if m != nil {
return m.Unit
}
return ""
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*Attribute) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _Attribute_OneofMarshaler, _Attribute_OneofUnmarshaler, _Attribute_OneofSizer, []interface{}{
(*Attribute_FloatVal)(nil),
(*Attribute_IntVal)(nil),
(*Attribute_StringVal)(nil),
(*Attribute_BoolVal)(nil),
}
}
func _Attribute_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*Attribute)
// value
switch x := m.Value.(type) {
case *Attribute_FloatVal:
b.EncodeVarint(1<<3 | proto.WireFixed64)
b.EncodeFixed64(math.Float64bits(x.FloatVal))
case *Attribute_IntVal:
b.EncodeVarint(2<<3 | proto.WireVarint)
b.EncodeVarint(uint64(x.IntVal))
case *Attribute_StringVal:
b.EncodeVarint(3<<3 | proto.WireBytes)
b.EncodeStringBytes(x.StringVal)
case *Attribute_BoolVal:
t := uint64(0)
if x.BoolVal {
t = 1
}
b.EncodeVarint(4<<3 | proto.WireVarint)
b.EncodeVarint(t)
case nil:
default:
return fmt.Errorf("Attribute.Value has unexpected type %T", x)
}
return nil
}
func _Attribute_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*Attribute)
switch tag {
case 1: // value.float_val
if wire != proto.WireFixed64 {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeFixed64()
m.Value = &Attribute_FloatVal{math.Float64frombits(x)}
return true, err
case 2: // value.int_val
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Value = &Attribute_IntVal{int64(x)}
return true, err
case 3: // value.string_val
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeStringBytes()
m.Value = &Attribute_StringVal{x}
return true, err
case 4: // value.bool_val
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Value = &Attribute_BoolVal{x != 0}
return true, err
default:
return false, nil
}
}
func _Attribute_OneofSizer(msg proto.Message) (n int) {
m := msg.(*Attribute)
// value
switch x := m.Value.(type) {
case *Attribute_FloatVal:
n += 1 // tag and wire
n += 8
case *Attribute_IntVal:
n += 1 // tag and wire
n += proto.SizeVarint(uint64(x.IntVal))
case *Attribute_StringVal:
n += 1 // tag and wire
n += proto.SizeVarint(uint64(len(x.StringVal)))
n += len(x.StringVal)
case *Attribute_BoolVal:
n += 1 // tag and wire
n += 1
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
}
return n
}
func init() {
proto.RegisterType((*Attribute)(nil), "hashicorp.nomad.plugins.shared.structs.Attribute")
}
func init() { proto.RegisterFile("attribute.proto", fileDescriptor_attribute_bfac9d16cf08e8f5) }
var fileDescriptor_attribute_bfac9d16cf08e8f5 = []byte{
// 209 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x34, 0xcf, 0xb1, 0x4e, 0xc3, 0x30,
0x10, 0x06, 0xe0, 0x98, 0x36, 0x4d, 0x7c, 0x0b, 0x92, 0xa7, 0x22, 0x84, 0xb0, 0x18, 0x90, 0x27,
0x2f, 0x3c, 0x01, 0x9d, 0x3c, 0x7b, 0x60, 0x60, 0x41, 0x97, 0x36, 0x34, 0x96, 0x8c, 0x1d, 0xd9,
0xe7, 0x3e, 0x0f, 0x8f, 0x8a, 0xec, 0x88, 0xe9, 0x4e, 0xff, 0x77, 0xff, 0x70, 0x70, 0x8f, 0x44,
0xc9, 0x4d, 0x85, 0x66, 0xbd, 0xa6, 0x48, 0x51, 0xbc, 0x2e, 0x98, 0x17, 0x77, 0x8e, 0x69, 0xd5,
0x21, 0xfe, 0xe0, 0x45, 0xaf, 0xbe, 0x5c, 0x5d, 0xc8, 0x3a, 0x2f, 0x98, 0xe6, 0x8b, 0xce, 0x94,
0xca, 0x99, 0xf2, 0xcb, 0x2f, 0x03, 0xfe, 0xfe, 0xdf, 0x15, 0x4f, 0xc0, 0xbf, 0x7d, 0x44, 0xfa,
0xba, 0xa1, 0x3f, 0x32, 0xc9, 0x14, 0x33, 0x9d, 0x1d, 0x5b, 0xf4, 0x81, 0x5e, 0x3c, 0xc0, 0xe0,
0xc2, 0x86, 0x77, 0x92, 0xa9, 0x9d, 0xe9, 0xec, 0xc1, 0x85, 0x46, 0xcf, 0x00, 0x99, 0x92, 0x0b,
0xd7, 0xa6, 0x3b, 0xc9, 0x14, 0x37, 0x9d, 0xe5, 0x5b, 0x56, 0x0f, 0x1e, 0x61, 0x9c, 0x62, 0xf4,
0x8d, 0xf7, 0x92, 0xa9, 0xd1, 0x74, 0x76, 0xa8, 0x49, 0x45, 0x01, 0xfb, 0x12, 0x1c, 0x1d, 0xfb,
0xda, 0xb3, 0x6d, 0x3f, 0x0d, 0xd0, 0xdf, 0xd0, 0x97, 0xf9, 0x34, 0x7c, 0xf6, 0xed, 0xa7, 0xe9,
0xd0, 0xc6, 0xdb, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x79, 0x18, 0x15, 0x15, 0xed, 0x00, 0x00,
0x00,
}

View file

@ -0,0 +1,25 @@
syntax = "proto3";
package hashicorp.nomad.plugins.shared.structs;
option go_package = "proto";
// Attribute is used to describe the value of an attribute, optionally
// specifying units
message Attribute {
oneof value {
// float_val exposes a floating point value.
double float_val = 1;
// int_numerator_val exposes a int value.
int64 int_val = 2;
// string_val exposes a string value.
string string_val = 3;
// bool_val exposes a boolean statistic.
bool bool_val = 4;
}
// unit gives the unit type: MHz, MB, etc.
string unit = 5;
}

View file

@ -0,0 +1,213 @@
package structs
import "sort"
var (
// 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{
{
Name: "KiB",
Base: UnitByte,
Multiplier: 1 << 10,
},
{
Name: "MiB",
Base: UnitByte,
Multiplier: 1 << 20,
},
{
Name: "GiB",
Base: UnitByte,
Multiplier: 1 << 30,
},
{
Name: "TiB",
Base: UnitByte,
Multiplier: 1 << 40,
},
{
Name: "PiB",
Base: UnitByte,
Multiplier: 1 << 50,
},
{
Name: "EiB",
Base: UnitByte,
Multiplier: 1 << 60,
},
}
decimalSIBytes = []*Unit{
{
Name: "kB",
Base: UnitByte,
Multiplier: Pow(1000, 1),
},
{
Name: "KB", // Alternative name for kB
Base: UnitByte,
Multiplier: Pow(1000, 1),
},
{
Name: "MB",
Base: UnitByte,
Multiplier: Pow(1000, 2),
},
{
Name: "GB",
Base: UnitByte,
Multiplier: Pow(1000, 3),
},
{
Name: "TB",
Base: UnitByte,
Multiplier: Pow(1000, 4),
},
{
Name: "PB",
Base: UnitByte,
Multiplier: Pow(1000, 5),
},
{
Name: "EB",
Base: UnitByte,
Multiplier: Pow(1000, 6),
},
}
binarySIByteRates = []*Unit{
{
Name: "KiB/s",
Base: UnitByteRate,
Multiplier: 1 << 10,
},
{
Name: "MiB/s",
Base: UnitByteRate,
Multiplier: 1 << 20,
},
{
Name: "GiB/s",
Base: UnitByteRate,
Multiplier: 1 << 30,
},
{
Name: "TiB/s",
Base: UnitByteRate,
Multiplier: 1 << 40,
},
{
Name: "PiB/s",
Base: UnitByteRate,
Multiplier: 1 << 50,
},
{
Name: "EiB/s",
Base: UnitByteRate,
Multiplier: 1 << 60,
},
}
decimalSIByteRates = []*Unit{
{
Name: "kB/s",
Base: UnitByteRate,
Multiplier: Pow(1000, 1),
},
{
Name: "KB/s", // Alternative name for kB/s
Base: UnitByteRate,
Multiplier: Pow(1000, 1),
},
{
Name: "MB/s",
Base: UnitByteRate,
Multiplier: Pow(1000, 2),
},
{
Name: "GB/s",
Base: UnitByteRate,
Multiplier: Pow(1000, 3),
},
{
Name: "TB/s",
Base: UnitByteRate,
Multiplier: Pow(1000, 4),
},
{
Name: "PB/s",
Base: UnitByteRate,
Multiplier: Pow(1000, 5),
},
{
Name: "EB/s",
Base: UnitByteRate,
Multiplier: Pow(1000, 6),
},
}
hertz = []*Unit{
{
Name: "MHz",
Base: UnitHertz,
Multiplier: Pow(1000, 1),
},
{
Name: "GHz",
Base: UnitHertz,
Multiplier: Pow(1000, 3),
},
}
watts = []*Unit{
{
Name: "mW",
Base: UnitWatt,
Multiplier: Pow(10, 3),
InverseMultiplier: true,
},
{
Name: "W",
Base: UnitWatt,
Multiplier: 1,
},
{
Name: "kW",
Base: UnitWatt,
Multiplier: Pow(10, 3),
},
{
Name: "MW",
Base: UnitWatt,
Multiplier: Pow(10, 6),
},
{
Name: "GW",
Base: UnitWatt,
Multiplier: Pow(10, 9),
},
}
)
func init() {
// Build the index
for _, units := range [][]*Unit{binarySIBytes, decimalSIBytes, binarySIByteRates, decimalSIByteRates, watts, hertz} {
for _, unit := range units {
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

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