Merge pull request #4978 from hashicorp/f-device-tweaks

Display device attributes in `nomad node status -verbose`
This commit is contained in:
Mahmood Ali
2018-12-12 19:45:07 -05:00
committed by GitHub
9 changed files with 266 additions and 87 deletions

View File

@@ -7,6 +7,7 @@ import (
"strconv"
"time"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs"
)
@@ -673,9 +674,9 @@ func (v *StatValue) String() string {
case v.StringVal != nil:
return *v.StringVal
case v.FloatNumeratorVal != nil:
str := strconv.FormatFloat(*v.FloatNumeratorVal, 'f', -1, 64)
str := helper.FormatFloat(*v.FloatNumeratorVal, 3)
if v.FloatDenominatorVal != nil {
str += " / " + strconv.FormatFloat(*v.FloatDenominatorVal, 'f', -1, 64)
str += " / " + helper.FormatFloat(*v.FloatDenominatorVal, 3)
}
if v.Unit != "" {
@@ -683,7 +684,6 @@ func (v *StatValue) String() string {
}
return str
case v.IntNumeratorVal != nil:
str := strconv.FormatInt(*v.IntNumeratorVal, 10)
if v.IntDenominatorVal != nil {
str += " / " + strconv.FormatInt(*v.IntDenominatorVal, 10)

View File

@@ -1,6 +1,10 @@
package api
import "github.com/hashicorp/nomad/helper"
import (
"strconv"
"github.com/hashicorp/nomad/helper"
)
// Resources encapsulates the required resources of
// a given task or task group.
@@ -122,6 +126,10 @@ type NodeDeviceResource struct {
Attributes map[string]*Attribute
}
func (r NodeDeviceResource) ID() string {
return r.Vendor + "/" + r.Type + "/" + r.Name
}
// NodeDevice is an instance of a particular device.
type NodeDevice struct {
// ID is the ID of the device.
@@ -143,21 +151,44 @@ type NodeDevice struct {
// specifying units
type Attribute struct {
// Float is the float value for the attribute
Float *float64
FloatVal *float64 `json:"Float,omitempty"`
// Int is the int value for the attribute
Int *int64
IntVal *int64 `json:"Int,omitempty"`
// String is the string value for the attribute
String *string
StringVal *string `json:"String,omitempty"`
// Bool is the bool value for the attribute
Bool *bool
BoolVal *bool `json:"Bool,omitempty"`
// Unit is the optional unit for the set int or float value
Unit string
}
func (a Attribute) String() string {
switch {
case a.FloatVal != nil:
str := helper.FormatFloat(*a.FloatVal, 3)
if a.Unit != "" {
str += " " + a.Unit
}
return str
case a.IntVal != nil:
str := strconv.FormatInt(*a.IntVal, 10)
if a.Unit != "" {
str += " " + a.Unit
}
return str
case a.StringVal != nil:
return *a.StringVal
case a.BoolVal != nil:
return strconv.FormatBool(*a.BoolVal)
default:
return "<unknown>"
}
}
// NodeDeviceLocality stores information about the devices hardware locality on
// the node.
type NodeDeviceLocality struct {

View File

@@ -109,3 +109,15 @@ func printDeviceStats(ui cli.Ui, deviceGroupStats []*api.DeviceGroupStats) {
}
}
}
func getDeviceAttributes(d *api.NodeDeviceResource) []string {
attrs := []string{fmt.Sprintf("Device Group|%s", d.ID())}
for k, v := range d.Attributes {
attrs = append(attrs, k+"|"+v.String())
}
sort.Strings(attrs[1:])
return attrs
}

View File

@@ -247,3 +247,29 @@ func TestNodeStatusCommand_GetDeviceResources(t *testing.T) {
assert.Equal(t, expected, formattedDevices)
}
func TestGetDeviceAttributes(t *testing.T) {
d := &api.NodeDeviceResource{
Vendor: "Vendor",
Type: "Type",
Name: "Name",
Attributes: map[string]*api.Attribute{
"utilization": {
FloatVal: helper.Float64ToPtr(0.78),
Unit: "%",
},
"filesystem": {
StringVal: helper.StringToPtr("ext4"),
},
},
}
formattedDevices := getDeviceAttributes(d)
expected := []string{
"Device Group|Vendor/Type/Name",
"filesystem|ext4",
"utilization|0.78 %",
}
assert.Equal(t, expected, formattedDevices)
}

View File

@@ -418,6 +418,7 @@ func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int {
if c.verbose {
c.formatAttributes(node)
c.formatDeviceAttributes(node)
c.formatMeta(node)
}
return 0
@@ -528,6 +529,32 @@ func (c *NodeStatusCommand) formatAttributes(node *api.Node) {
c.Ui.Output(formatKV(attributes))
}
func (c *NodeStatusCommand) formatDeviceAttributes(node *api.Node) {
devices := node.NodeResources.Devices
if len(devices) == 0 {
return
}
sort.Slice(devices, func(i, j int) bool {
return devices[i].ID() < devices[j].ID()
})
first := true
for _, d := range devices {
if len(d.Attributes) == 0 {
continue
}
if first {
c.Ui.Output("\nDevice Group Attributes")
first = false
} else {
c.Ui.Output("")
}
c.Ui.Output(formatKV(getDeviceAttributes(d)))
}
}
func (c *NodeStatusCommand) formatMeta(node *api.Node) {
// Print the meta
keys := make([]string, 0, len(node.Meta))

View File

@@ -286,7 +286,7 @@ func statsForItem(statsItem *nvml.StatsData, timestamp time.Time) *device.Device
}
}
return &device.DeviceStats{
Summary: temperatureStat,
Summary: memoryStateStat,
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
PowerUsageAttr: powerUsageStat,

View File

@@ -447,9 +447,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -541,9 +542,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -634,9 +636,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -727,9 +730,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -821,9 +825,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -915,9 +920,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1009,9 +1015,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1103,9 +1110,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
StringVal: helper.StringToPtr(notAvailable),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1197,9 +1205,9 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
StringVal: helper.StringToPtr(notAvailable),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1290,9 +1298,9 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
StringVal: helper.StringToPtr(notAvailable),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1383,9 +1391,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1476,9 +1485,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1569,9 +1579,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1663,9 +1674,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1757,9 +1769,10 @@ func TestStatsForItem(t *testing.T) {
},
ExpectedResult: &device.DeviceStats{
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1913,9 +1926,10 @@ func TestStatsForGroup(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID1": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -1983,9 +1997,10 @@ func TestStatsForGroup(t *testing.T) {
},
"UUID2": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
IntDenominatorVal: helper.Int64ToPtr(2),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2053,9 +2068,10 @@ func TestStatsForGroup(t *testing.T) {
},
"UUID3": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(3),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(3),
IntDenominatorVal: helper.Int64ToPtr(3),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2234,9 +2250,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID1": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2311,9 +2328,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID2": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
IntDenominatorVal: helper.Int64ToPtr(2),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2388,9 +2406,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID3": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(3),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(3),
IntDenominatorVal: helper.Int64ToPtr(3),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2545,9 +2564,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID1": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2622,9 +2642,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID3": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(3),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(3),
IntDenominatorVal: helper.Int64ToPtr(3),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2692,9 +2713,10 @@ func TestWriteStatsToChannel(t *testing.T) {
},
"UUID2": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
IntDenominatorVal: helper.Int64ToPtr(2),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2848,9 +2870,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID1": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(1),
IntDenominatorVal: helper.Int64ToPtr(1),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{
@@ -2925,9 +2948,10 @@ func TestWriteStatsToChannel(t *testing.T) {
InstanceStats: map[string]*device.DeviceStats{
"UUID2": {
Summary: &structs.StatValue{
Unit: TemperatureUnit,
Desc: TemperatureDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
Unit: MemoryStateUnit,
Desc: MemoryStateDesc,
IntNumeratorVal: helper.Int64ToPtr(2),
IntDenominatorVal: helper.Int64ToPtr(2),
},
Stats: &structs.StatObject{
Attributes: map[string]*structs.StatValue{

View File

@@ -4,6 +4,8 @@ import (
"crypto/sha512"
"fmt"
"regexp"
"strconv"
"strings"
"time"
multierror "github.com/hashicorp/go-multierror"
@@ -344,3 +346,26 @@ func CheckHCLKeys(node ast.Node, valid []string) error {
return result
}
// FormatFloat converts the floating-point number f to a string,
// after rounding it to the passed unit.
//
// Uses 'f' format (-ddd.dddddd, no exponent), and uses at most
// maxPrec digits after the decimal point.
func FormatFloat(f float64, maxPrec int) string {
v := strconv.FormatFloat(f, 'f', -1, 64)
idx := strings.LastIndex(v, ".")
if idx == -1 {
return v
}
sublen := idx + maxPrec + 1
if sublen > len(v) {
sublen = len(v)
}
return v[:sublen]
return v
}

View File

@@ -4,6 +4,8 @@ import (
"reflect"
"sort"
"testing"
"github.com/stretchr/testify/require"
)
func TestSliceStringIsSubset(t *testing.T) {
@@ -87,3 +89,35 @@ func BenchmarkCleanEnvVar(b *testing.B) {
CleanEnvVar(in, replacement)
}
}
func TestFormatRoundedFloat(t *testing.T) {
cases := []struct {
input float64
expected string
}{
{
1323,
"1323",
},
{
10.321,
"10.321",
},
{
100000.31324324,
"100000.313",
},
{
100000.3,
"100000.3",
},
{
0.7654321,
"0.765",
},
}
for _, c := range cases {
require.Equal(t, c.expected, FormatFloat(c.input, 3))
}
}