From 364180b3965ac84ba16cadf16917a6757987e0bc Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Thu, 11 Oct 2018 23:18:26 -0700 Subject: [PATCH 1/4] Units defined and parsing --- plugins/shared/structs/attribute.go | 129 ++++++++++ plugins/shared/structs/attribute_test.go | 125 ++++++++++ plugins/shared/structs/proto/attribute.pb.go | 250 +++++++++++++++++++ plugins/shared/structs/proto/attribute.proto | 25 ++ plugins/shared/structs/units.go | 198 +++++++++++++++ plugins/shared/structs/util.go | 35 +++ 6 files changed, 762 insertions(+) create mode 100644 plugins/shared/structs/attribute.go create mode 100644 plugins/shared/structs/attribute_test.go create mode 100644 plugins/shared/structs/proto/attribute.pb.go create mode 100644 plugins/shared/structs/proto/attribute.proto create mode 100644 plugins/shared/structs/units.go create mode 100644 plugins/shared/structs/util.go diff --git a/plugins/shared/structs/attribute.go b/plugins/shared/structs/attribute.go new file mode 100644 index 000000000..38a6909fa --- /dev/null +++ b/plugins/shared/structs/attribute.go @@ -0,0 +1,129 @@ +package structs + +import ( + "fmt" + "regexp" + "strconv" +) + +// 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 uint64 + + // 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 +} + +// 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) + } + } + + return nil +} + +var ( + // numericWithUnits matches only if it is a integer or float ending with + // units. It has two capture groups, one for the numeric value and one for + // the unit value + numericWithUnits = regexp.MustCompile(`^([-]?(?:[0-9]+|[0-9]+\.[0-9]+|\.[0-9]+))\s*([a-zA-Z]+\/?[a-zA-z]+|[a-zA-Z])$`) +) + +func ParseAttribute(input string) *Attribute { + // Try to parse as a bool + b, err := strconv.ParseBool(input) + if err == nil { + return &Attribute{Bool: b} + } + + // Try to parse as a number. + + // Check if the string is a number ending with potential units + if matches := numericWithUnits.FindStringSubmatch(input); len(matches) == 3 { + numeric := matches[1] + unit := matches[2] + + // Check if we know about the unit. If we don't we can only treat this + // as a string + if _, ok := UnitIndex[unit]; !ok { + return &Attribute{String: input} + } + + // Try to parse as an int + i, err := strconv.ParseInt(numeric, 10, 64) + if err == nil { + return &Attribute{Int: i, Unit: unit} + } + + // Try to parse as a float + f, err := strconv.ParseFloat(numeric, 64) + if err == nil { + return &Attribute{Float: f, Unit: unit} + } + } + + // Try to parse as an int + i, err := strconv.ParseInt(input, 10, 64) + if err == nil { + return &Attribute{Int: i} + } + + // Try to parse as a float + f, err := strconv.ParseFloat(input, 64) + if err == nil { + return &Attribute{Float: f} + } + + return &Attribute{String: input} +} diff --git a/plugins/shared/structs/attribute_test.go b/plugins/shared/structs/attribute_test.go new file mode 100644 index 000000000..68342c215 --- /dev/null +++ b/plugins/shared/structs/attribute_test.go @@ -0,0 +1,125 @@ +package structs + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAttribute_ParseAndValidate(t *testing.T) { + cases := []struct { + Input string + Expected *Attribute + }{ + { + Input: "true", + Expected: &Attribute{ + Bool: true, + }, + }, + { + Input: "false", + Expected: &Attribute{ + Bool: false, + }, + }, + { + Input: "100", + Expected: &Attribute{ + Int: 100, + }, + }, + { + Input: "-100", + Expected: &Attribute{ + Int: -100, + }, + }, + { + Input: "-1.0", + Expected: &Attribute{ + Float: -1.0, + }, + }, + { + Input: "-100.25", + Expected: &Attribute{ + Float: -100.25, + }, + }, + { + Input: "1.01", + Expected: &Attribute{ + Float: 1.01, + }, + }, + { + Input: "100.25", + Expected: &Attribute{ + Float: 100.25, + }, + }, + { + Input: "foobar", + Expected: &Attribute{ + String: "foobar", + }, + }, + { + Input: "foo123bar", + Expected: &Attribute{ + String: "foo123bar", + }, + }, + { + Input: "100MB", + Expected: &Attribute{ + Int: 100, + Unit: "MB", + }, + }, + { + Input: "-100MHz", + Expected: &Attribute{ + Int: -100, + Unit: "MHz", + }, + }, + { + Input: "-1.0MB/s", + Expected: &Attribute{ + Float: -1.0, + Unit: "MB/s", + }, + }, + { + Input: "-100.25GiB/s", + Expected: &Attribute{ + Float: -100.25, + Unit: "GiB/s", + }, + }, + { + Input: "1.01TB", + Expected: &Attribute{ + Float: 1.01, + Unit: "TB", + }, + }, + { + Input: "100.25mW", + Expected: &Attribute{ + Float: 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()) + }) + } +} diff --git a/plugins/shared/structs/proto/attribute.pb.go b/plugins/shared/structs/proto/attribute.pb.go new file mode 100644 index 000000000..af3dd4d48 --- /dev/null +++ b/plugins/shared/structs/proto/attribute.pb.go @@ -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, +} diff --git a/plugins/shared/structs/proto/attribute.proto b/plugins/shared/structs/proto/attribute.proto new file mode 100644 index 000000000..4df716390 --- /dev/null +++ b/plugins/shared/structs/proto/attribute.proto @@ -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; +} + diff --git a/plugins/shared/structs/units.go b/plugins/shared/structs/units.go new file mode 100644 index 000000000..86296433a --- /dev/null +++ b/plugins/shared/structs/units.go @@ -0,0 +1,198 @@ +package structs. + +var ( + UnitIndex = make(map[string]*Unit, len(binarySIBytes)+len(decimalSIBytes)+len(binarySIByteRates)+len(decimalSIByteRates)+len(watts)+len(hertz)) + + binarySIBytes = []*Unit{ + &Unit{ + Name: "KiB", + Base: UnitByte, + Multiplier: 1 << 10, + }, + &Unit{ + Name: "MiB", + Base: UnitByte, + Multiplier: 1 << 20, + }, + &Unit{ + Name: "GiB", + Base: UnitByte, + Multiplier: 1 << 30, + }, + &Unit{ + Name: "TiB", + Base: UnitByte, + Multiplier: 1 << 40, + }, + &Unit{ + Name: "PiB", + Base: UnitByte, + Multiplier: 1 << 50, + }, + &Unit{ + Name: "EiB", + Base: UnitByte, + Multiplier: 1 << 60, + }, + } + + decimalSIBytes = []*Unit{ + &Unit{ + Name: "kB", + Base: UnitByte, + Multiplier: Pow(1000, 1), + }, + &Unit{ + Name: "KB", // Alternative name for kB + Base: UnitByte, + Multiplier: Pow(1000, 1), + }, + &Unit{ + Name: "MB", + Base: UnitByte, + Multiplier: Pow(1000, 2), + }, + &Unit{ + Name: "GB", + Base: UnitByte, + Multiplier: Pow(1000, 3), + }, + &Unit{ + Name: "TB", + Base: UnitByte, + Multiplier: Pow(1000, 4), + }, + &Unit{ + Name: "PB", + Base: UnitByte, + Multiplier: Pow(1000, 5), + }, + &Unit{ + Name: "EB", + Base: UnitByte, + Multiplier: Pow(1000, 6), + }, + } + + binarySIByteRates = []*Unit{ + &Unit{ + Name: "KiB/s", + Base: UnitByteRate, + Multiplier: 1 << 10, + }, + &Unit{ + Name: "MiB/s", + Base: UnitByteRate, + Multiplier: 1 << 20, + }, + &Unit{ + Name: "GiB/s", + Base: UnitByteRate, + Multiplier: 1 << 30, + }, + &Unit{ + Name: "TiB/s", + Base: UnitByteRate, + Multiplier: 1 << 40, + }, + &Unit{ + Name: "PiB/s", + Base: UnitByteRate, + Multiplier: 1 << 50, + }, + &Unit{ + Name: "EiB/s", + Base: UnitByteRate, + Multiplier: 1 << 60, + }, + } + + decimalSIByteRates = []*Unit{ + &Unit{ + Name: "kB/s", + Base: UnitByteRate, + Multiplier: Pow(1000, 1), + }, + &Unit{ + Name: "KB/s", // Alternative name for kB/s + Base: UnitByteRate, + Multiplier: Pow(1000, 1), + }, + &Unit{ + Name: "MB/s", + Base: UnitByteRate, + Multiplier: Pow(1000, 2), + }, + &Unit{ + Name: "GB/s", + Base: UnitByteRate, + Multiplier: Pow(1000, 3), + }, + &Unit{ + Name: "TB/s", + Base: UnitByteRate, + Multiplier: Pow(1000, 4), + }, + &Unit{ + Name: "PB/s", + Base: UnitByteRate, + Multiplier: Pow(1000, 5), + }, + &Unit{ + Name: "EB/s", + Base: UnitByteRate, + Multiplier: Pow(1000, 6), + }, + } + + hertz = []*Unit{ + &Unit{ + Name: "MHz", + Base: UnitHertz, + Multiplier: Pow(1000, 1), + }, + &Unit{ + Name: "GHz", + Base: UnitHertz, + Multiplier: Pow(1000, 3), + }, + } + + watts = []*Unit{ + &Unit{ + Name: "mW", + Base: UnitWatt, + Multiplier: Pow(10, 3), + InverseMultiplier: true, + }, + &Unit{ + Name: "W", + Base: UnitWatt, + Multiplier: 1, + }, + &Unit{ + Name: "kW", + Base: UnitWatt, + Multiplier: Pow(10, 3), + }, + &Unit{ + Name: "MW", + Base: UnitWatt, + Multiplier: Pow(10, 6), + }, + &Unit{ + 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 + } + } +} diff --git a/plugins/shared/structs/util.go b/plugins/shared/structs/util.go new file mode 100644 index 000000000..1f2ec7e23 --- /dev/null +++ b/plugins/shared/structs/util.go @@ -0,0 +1,35 @@ +package structs + +import "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 = in.GetBoolVal() + case *proto.Attribute_FloatVal: + out.Float = in.GetFloatVal() + case *proto.Attribute_IntVal: + out.Int = in.GetIntVal() + case *proto.Attribute_StringVal: + out.String = in.GetStringVal() + default: + } + + return out +} + +func Pow(a, b uint64) uint64 { + var p uint64 = 1 + for b > 0 { + if b&1 != 0 { + p *= a + } + b >>= 1 + a *= a + } + return p +} From cbb5f2111287dd77fb8e9b1ec787f7aed396b8eb Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Fri, 12 Oct 2018 15:25:34 -0700 Subject: [PATCH 2/4] New parser and comparison --- helper/funcs.go | 5 + plugins/shared/structs/attribute.go | 300 +++++++++++- plugins/shared/structs/attribute_test.go | 595 ++++++++++++++++++++++- plugins/shared/structs/units.go | 19 +- plugins/shared/structs/util.go | 17 +- 5 files changed, 886 insertions(+), 50 deletions(-) diff --git a/helper/funcs.go b/helper/funcs.go index 4d8f53b1a..083ab865b 100644 --- a/helper/funcs.go +++ b/helper/funcs.go @@ -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 diff --git a/plugins/shared/structs/attribute.go b/plugins/shared/structs/attribute.go index 38a6909fa..a426bc422 100644 --- a/plugins/shared/structs/attribute.go +++ b/plugins/shared/structs/attribute.go @@ -2,8 +2,18 @@ package structs import ( "fmt" - "regexp" + "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 @@ -27,7 +37,7 @@ type Unit struct { Base BaseUnit // 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: // Base / Multiplier. For example a mW is a W/1000. @@ -47,83 +57,323 @@ func (u *Unit) Comparable(o *Unit) bool { // specifying units type Attribute struct { // Float is the float value for the attribute - Float float64 + Float *float64 // Int is the int value for the attribute - Int int64 + Int *int64 // String is the string value for the attribute - String string + String *string // 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 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 } -var ( - // numericWithUnits matches only if it is a integer or float ending with - // units. It has two capture groups, one for the numeric value and one for - // the unit value - numericWithUnits = regexp.MustCompile(`^([-]?(?:[0-9]+|[0-9]+\.[0-9]+|\.[0-9]+))\s*([a-zA-Z]+\/?[a-zA-z]+|[a-zA-Z])$`) -) +// 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.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 { + 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: b} + return &Attribute{Bool: helper.BoolToPtr(b)} } - // Try to parse as a number. - // Check if the string is a number ending with potential units - if matches := numericWithUnits.FindStringSubmatch(input); len(matches) == 3 { - numeric := matches[1] - unit := matches[2] + 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 _, ok := UnitIndex[unit]; !ok { - return &Attribute{String: input} + 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: i, Unit: unit} + 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: f, Unit: unit} + 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: i} + return &Attribute{Int: helper.Int64ToPtr(i)} } // Try to parse as a float f, err := strconv.ParseFloat(input, 64) if err == nil { - return &Attribute{Float: f} + return &Attribute{Float: helper.Float64ToPtr(f)} } - return &Attribute{String: input} + return &Attribute{String: helper.StringToPtr(input)} } diff --git a/plugins/shared/structs/attribute_test.go b/plugins/shared/structs/attribute_test.go index 68342c215..fa9197206 100644 --- a/plugins/shared/structs/attribute_test.go +++ b/plugins/shared/structs/attribute_test.go @@ -1,11 +1,546 @@ 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 @@ -14,102 +549,102 @@ func TestAttribute_ParseAndValidate(t *testing.T) { { Input: "true", Expected: &Attribute{ - Bool: true, + Bool: helper.BoolToPtr(true), }, }, { Input: "false", Expected: &Attribute{ - Bool: false, + Bool: helper.BoolToPtr(false), }, }, { Input: "100", Expected: &Attribute{ - Int: 100, + Int: helper.Int64ToPtr(100), }, }, { Input: "-100", Expected: &Attribute{ - Int: -100, + Int: helper.Int64ToPtr(-100), }, }, { Input: "-1.0", Expected: &Attribute{ - Float: -1.0, + Float: helper.Float64ToPtr(-1.0), }, }, { Input: "-100.25", Expected: &Attribute{ - Float: -100.25, + Float: helper.Float64ToPtr(-100.25), }, }, { Input: "1.01", Expected: &Attribute{ - Float: 1.01, + Float: helper.Float64ToPtr(1.01), }, }, { Input: "100.25", Expected: &Attribute{ - Float: 100.25, + Float: helper.Float64ToPtr(100.25), }, }, { Input: "foobar", Expected: &Attribute{ - String: "foobar", + String: helper.StringToPtr("foobar"), }, }, { Input: "foo123bar", Expected: &Attribute{ - String: "foo123bar", + String: helper.StringToPtr("foo123bar"), }, }, { Input: "100MB", Expected: &Attribute{ - Int: 100, + Int: helper.Int64ToPtr(100), Unit: "MB", }, }, { Input: "-100MHz", Expected: &Attribute{ - Int: -100, + Int: helper.Int64ToPtr(-100), Unit: "MHz", }, }, { Input: "-1.0MB/s", Expected: &Attribute{ - Float: -1.0, + Float: helper.Float64ToPtr(-1.0), Unit: "MB/s", }, }, { Input: "-100.25GiB/s", Expected: &Attribute{ - Float: -100.25, + Float: helper.Float64ToPtr(-100.25), Unit: "GiB/s", }, }, { Input: "1.01TB", Expected: &Attribute{ - Float: 1.01, + Float: helper.Float64ToPtr(1.01), Unit: "TB", }, }, { Input: "100.25mW", Expected: &Attribute{ - Float: 100.25, + Float: helper.Float64ToPtr(100.25), 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) + } + } +} diff --git a/plugins/shared/structs/units.go b/plugins/shared/structs/units.go index 86296433a..f77c0dd40 100644 --- a/plugins/shared/structs/units.go +++ b/plugins/shared/structs/units.go @@ -1,7 +1,17 @@ -package structs. +package structs + +import "sort" 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{ &Unit{ @@ -193,6 +203,11 @@ func init() { 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]) + }) } diff --git a/plugins/shared/structs/util.go b/plugins/shared/structs/util.go index 1f2ec7e23..dec6b9ad0 100644 --- a/plugins/shared/structs/util.go +++ b/plugins/shared/structs/util.go @@ -1,6 +1,9 @@ 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 { out := &Attribute{ @@ -9,21 +12,21 @@ func ConvertProtoAttribute(in *proto.Attribute) *Attribute { switch in.Value.(type) { case *proto.Attribute_BoolVal: - out.Bool = in.GetBoolVal() + out.Bool = helper.BoolToPtr(in.GetBoolVal()) case *proto.Attribute_FloatVal: - out.Float = in.GetFloatVal() + out.Float = helper.Float64ToPtr(in.GetFloatVal()) case *proto.Attribute_IntVal: - out.Int = in.GetIntVal() + out.Int = helper.Int64ToPtr(in.GetIntVal()) case *proto.Attribute_StringVal: - out.String = in.GetStringVal() + out.String = helper.StringToPtr(in.GetStringVal()) default: } return out } -func Pow(a, b uint64) uint64 { - var p uint64 = 1 +func Pow(a, b int64) int64 { + var p int64 = 1 for b > 0 { if b&1 != 0 { p *= a From c31a234fb3cccecaa9f3a9935fcfa89027c6ac1e Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Fri, 12 Oct 2018 15:34:21 -0700 Subject: [PATCH 3/4] fmt -s --- plugins/shared/structs/units.go | 66 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/plugins/shared/structs/units.go b/plugins/shared/structs/units.go index f77c0dd40..5d418cb11 100644 --- a/plugins/shared/structs/units.go +++ b/plugins/shared/structs/units.go @@ -14,32 +14,32 @@ var ( lengthSortedUnits = make([]string, 0, numUnits) binarySIBytes = []*Unit{ - &Unit{ + { Name: "KiB", Base: UnitByte, Multiplier: 1 << 10, }, - &Unit{ + { Name: "MiB", Base: UnitByte, Multiplier: 1 << 20, }, - &Unit{ + { Name: "GiB", Base: UnitByte, Multiplier: 1 << 30, }, - &Unit{ + { Name: "TiB", Base: UnitByte, Multiplier: 1 << 40, }, - &Unit{ + { Name: "PiB", Base: UnitByte, Multiplier: 1 << 50, }, - &Unit{ + { Name: "EiB", Base: UnitByte, Multiplier: 1 << 60, @@ -47,37 +47,37 @@ var ( } decimalSIBytes = []*Unit{ - &Unit{ + { Name: "kB", Base: UnitByte, Multiplier: Pow(1000, 1), }, - &Unit{ + { Name: "KB", // Alternative name for kB Base: UnitByte, Multiplier: Pow(1000, 1), }, - &Unit{ + { Name: "MB", Base: UnitByte, Multiplier: Pow(1000, 2), }, - &Unit{ + { Name: "GB", Base: UnitByte, Multiplier: Pow(1000, 3), }, - &Unit{ + { Name: "TB", Base: UnitByte, Multiplier: Pow(1000, 4), }, - &Unit{ + { Name: "PB", Base: UnitByte, Multiplier: Pow(1000, 5), }, - &Unit{ + { Name: "EB", Base: UnitByte, Multiplier: Pow(1000, 6), @@ -85,32 +85,32 @@ var ( } binarySIByteRates = []*Unit{ - &Unit{ + { Name: "KiB/s", Base: UnitByteRate, Multiplier: 1 << 10, }, - &Unit{ + { Name: "MiB/s", Base: UnitByteRate, Multiplier: 1 << 20, }, - &Unit{ + { Name: "GiB/s", Base: UnitByteRate, Multiplier: 1 << 30, }, - &Unit{ + { Name: "TiB/s", Base: UnitByteRate, Multiplier: 1 << 40, }, - &Unit{ + { Name: "PiB/s", Base: UnitByteRate, Multiplier: 1 << 50, }, - &Unit{ + { Name: "EiB/s", Base: UnitByteRate, Multiplier: 1 << 60, @@ -118,37 +118,37 @@ var ( } decimalSIByteRates = []*Unit{ - &Unit{ + { Name: "kB/s", Base: UnitByteRate, Multiplier: Pow(1000, 1), }, - &Unit{ + { Name: "KB/s", // Alternative name for kB/s Base: UnitByteRate, Multiplier: Pow(1000, 1), }, - &Unit{ + { Name: "MB/s", Base: UnitByteRate, Multiplier: Pow(1000, 2), }, - &Unit{ + { Name: "GB/s", Base: UnitByteRate, Multiplier: Pow(1000, 3), }, - &Unit{ + { Name: "TB/s", Base: UnitByteRate, Multiplier: Pow(1000, 4), }, - &Unit{ + { Name: "PB/s", Base: UnitByteRate, Multiplier: Pow(1000, 5), }, - &Unit{ + { Name: "EB/s", Base: UnitByteRate, Multiplier: Pow(1000, 6), @@ -156,12 +156,12 @@ var ( } hertz = []*Unit{ - &Unit{ + { Name: "MHz", Base: UnitHertz, Multiplier: Pow(1000, 1), }, - &Unit{ + { Name: "GHz", Base: UnitHertz, Multiplier: Pow(1000, 3), @@ -169,28 +169,28 @@ var ( } watts = []*Unit{ - &Unit{ + { Name: "mW", Base: UnitWatt, Multiplier: Pow(10, 3), InverseMultiplier: true, }, - &Unit{ + { Name: "W", Base: UnitWatt, Multiplier: 1, }, - &Unit{ + { Name: "kW", Base: UnitWatt, Multiplier: Pow(10, 3), }, - &Unit{ + { Name: "MW", Base: UnitWatt, Multiplier: Pow(10, 6), }, - &Unit{ + { Name: "GW", Base: UnitWatt, Multiplier: Pow(10, 9), From 4098d2e99c036c22be81dc00d07c377d88c878d4 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Mon, 15 Oct 2018 13:15:58 -0700 Subject: [PATCH 4/4] fix typos --- plugins/shared/structs/attribute.go | 36 ++++++++++++------------ plugins/shared/structs/attribute_test.go | 1 - 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/plugins/shared/structs/attribute.go b/plugins/shared/structs/attribute.go index a426bc422..76d3f2bfb 100644 --- a/plugins/shared/structs/attribute.go +++ b/plugins/shared/structs/attribute.go @@ -143,26 +143,26 @@ func (a *Attribute) Compare(b *Attribute) (int, bool) { return 0, false } - return a.comparitor()(b) + return a.comparator()(b) } -// comparitor returns the comparitor function for the attribute -func (a *Attribute) comparitor() compareFn { +// comparator returns the comparator function for the attribute +func (a *Attribute) comparator() compareFn { if a.Bool != nil { - return a.boolComparitor + return a.boolComparator } if a.String != nil { - return a.stringComparitor + return a.stringComparator } if a.Int != nil || a.Float != nil { - return a.numberComparitor + return a.numberComparator } - return nullComparitor + return nullComparator } -// boolComparitor compares two boolean attributes -func (a *Attribute) boolComparitor(b *Attribute) (int, bool) { +// boolComparator compares two boolean attributes +func (a *Attribute) boolComparator(b *Attribute) (int, bool) { if *a.Bool == *b.Bool { return 0, true } @@ -170,17 +170,17 @@ func (a *Attribute) boolComparitor(b *Attribute) (int, bool) { return 1, true } -// stringComparitor compares two string attributes -func (a *Attribute) stringComparitor(b *Attribute) (int, bool) { +// stringComparator compares two string attributes +func (a *Attribute) stringComparator(b *Attribute) (int, bool) { return strings.Compare(*a.String, *b.String), true } -// numberComparitor compares two number attributes, having either Int or Float +// numberComparator compares two number attributes, having either Int or Float // set. -func (a *Attribute) numberComparitor(b *Attribute) (int, bool) { +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.intComparitor(b) + return a.intComparator(b) } // Push both into the float space @@ -193,8 +193,8 @@ func (a *Attribute) numberComparitor(b *Attribute) (int, bool) { return af.Cmp(bf), true } -// intComparitor compares two integer attributes. -func (a *Attribute) intComparitor(b *Attribute) (int, bool) { +// intComparator compares two integer attributes. +func (a *Attribute) intComparator(b *Attribute) (int, bool) { ai := a.getInt() bi := b.getInt() @@ -207,9 +207,9 @@ func (a *Attribute) intComparitor(b *Attribute) (int, bool) { } } -// nullComparitor always returns false and is used when no comparison function +// nullComparator always returns false and is used when no comparison function // is possible -func nullComparitor(*Attribute) (int, bool) { +func nullComparator(*Attribute) (int, bool) { return 0, false } diff --git a/plugins/shared/structs/attribute_test.go b/plugins/shared/structs/attribute_test.go index fa9197206..f49f43e88 100644 --- a/plugins/shared/structs/attribute_test.go +++ b/plugins/shared/structs/attribute_test.go @@ -679,7 +679,6 @@ func BenchmarkParse(b *testing.B) { "100.25mW", } - // run the Fib function b.N times for n := 0; n < b.N; n++ { for _, c := range cases { ParseAttribute(c)