Units defined and parsing

This commit is contained in:
Alex Dadgar 2018-10-11 23:18:26 -07:00
parent be91b5f3ac
commit 364180b396
6 changed files with 762 additions and 0 deletions

View file

@ -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}
}

View file

@ -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())
})
}
}

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,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
}
}
}

View file

@ -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
}