From f2e1cefda117ca319a5875ef2c2cff883b53eb8b Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Thu, 11 Oct 2018 23:18:26 -0700 Subject: [PATCH] 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 +}