From 61fcebf296d895f5954e0679bb362219e62967cf Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Sat, 11 Jun 2016 14:40:51 -0700 Subject: [PATCH 1/3] initial refactor and cleanup --- command/meta.go | 3 + command/node_status.go | 165 ++++++++++++++++------------- demo/vagrant/client1.hcl | 3 + website/helpers/command_helpers.rb | 2 + 4 files changed, 102 insertions(+), 71 deletions(-) diff --git a/command/meta.go b/command/meta.go index 354346d83..2f1ecbb25 100644 --- a/command/meta.go +++ b/command/meta.go @@ -118,6 +118,9 @@ func generalOptionsUsage() string { The region of the Nomad servers to forward commands to. Overrides the NOMAD_REGION environment variable if set. Defaults to the Agent's local region. + + -no-color + Disables colored command output. ` return strings.TrimSpace(helpText) } diff --git a/command/node_status.go b/command/node_status.go index 72302834d..10e29ab55 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -4,7 +4,6 @@ import ( "fmt" "math" "sort" - "strconv" "strings" "time" @@ -14,9 +13,20 @@ import ( "github.com/hashicorp/nomad/api" ) +const ( + // floatFormat is a format string for formatting floats. + floatFormat = "#,###.##" +) + type NodeStatusCommand struct { Meta - color *colorstring.Colorize + color *colorstring.Colorize + length int + short bool + verbose bool + list_allocs bool + self bool + stats bool } func (c *NodeStatusCommand) Help() string { @@ -62,16 +72,14 @@ func (c *NodeStatusCommand) Synopsis() string { } func (c *NodeStatusCommand) Run(args []string) int { - var short, verbose, list_allocs, self, stats bool - var hostStats *api.HostStats flags := c.Meta.FlagSet("node-status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } - flags.BoolVar(&short, "short", false, "") - flags.BoolVar(&verbose, "verbose", false, "") - flags.BoolVar(&list_allocs, "allocs", false, "") - flags.BoolVar(&self, "self", false, "") - flags.BoolVar(&stats, "stats", false, "") + flags.BoolVar(&c.short, "short", false, "") + flags.BoolVar(&c.verbose, "verbose", false, "") + flags.BoolVar(&c.list_allocs, "allocs", false, "") + flags.BoolVar(&c.self, "self", false, "") + flags.BoolVar(&c.stats, "stats", false, "") if err := flags.Parse(args); err != nil { return 1 @@ -85,9 +93,9 @@ func (c *NodeStatusCommand) Run(args []string) int { } // Truncate the id unless full length is requested - length := shortId - if verbose { - length = fullId + c.length = shortId + if c.verbose { + c.length = fullId } // Get the HTTP client @@ -98,7 +106,7 @@ func (c *NodeStatusCommand) Run(args []string) int { } // Use list mode if no node name was provided - if len(args) == 0 && !self { + if len(args) == 0 && !c.self { // Query the node info nodes, _, err := client.Nodes().List(nil) if err != nil { @@ -113,20 +121,20 @@ func (c *NodeStatusCommand) Run(args []string) int { // Format the nodes list out := make([]string, len(nodes)+1) - if list_allocs { + if c.list_allocs { out[0] = "ID|DC|Name|Class|Drain|Status|Running Allocs" } else { out[0] = "ID|DC|Name|Class|Drain|Status" } for i, node := range nodes { - if list_allocs { + if c.list_allocs { numAllocs, err := getRunningAllocs(client, node.ID) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) return 1 } out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s|%v", - limit(node.ID, length), + limit(node.ID, c.length), node.Datacenter, node.Name, node.NodeClass, @@ -135,7 +143,7 @@ func (c *NodeStatusCommand) Run(args []string) int { len(numAllocs)) } else { out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s", - limit(node.ID, length), + limit(node.ID, c.length), node.Datacenter, node.Name, node.NodeClass, @@ -151,7 +159,7 @@ func (c *NodeStatusCommand) Run(args []string) int { // Query the specific node nodeID := "" - if !self { + if !c.self { nodeID = args[0] } else { var err error @@ -187,7 +195,7 @@ func (c *NodeStatusCommand) Run(args []string) int { out[0] = "ID|DC|Name|Class|Drain|Status" for i, node := range nodes { out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s", - limit(node.ID, length), + limit(node.ID, c.length), node.Datacenter, node.Name, node.NodeClass, @@ -205,11 +213,16 @@ func (c *NodeStatusCommand) Run(args []string) int { return 1 } - var nodeStatsErr error - hostStats, nodeStatsErr = client.Nodes().Stats(node.ID, nil) - // Format the output + return c.formatNode(client, node) +} + +func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int { + // Get the host stats + hostStats, nodeStatsErr := client.Nodes().Stats(node.ID, nil) + + // Format the header output basic := []string{ - fmt.Sprintf("[bold]Node ID[reset]|%s", limit(node.ID, length)), + fmt.Sprintf("Node ID|%s", limit(node.ID, c.length)), fmt.Sprintf("Name|%s", node.Name), fmt.Sprintf("Class|%s", node.NodeClass), fmt.Sprintf("DC|%s", node.Datacenter), @@ -222,77 +235,88 @@ func (c *NodeStatusCommand) Run(args []string) int { } c.Ui.Output(c.Colorize().Color(formatKV(basic))) - if !short { + if !c.short { allocatedResources, err := getAllocatedResources(client, node) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying node resources: %s", err)) return 1 } - c.Ui.Output(c.Colorize().Color("\n[bold]==> Resource Utilization (Allocated)[reset]")) + c.Ui.Output(c.Colorize().Color("\n[bold]Allocated Resources[reset]")) c.Ui.Output(formatList(allocatedResources)) actualResources, err := getActualResources(hostStats, node) if err == nil { - c.Ui.Output(c.Colorize().Color("\n[bold]==> Resource Utilization (Actual)[reset]")) + c.Ui.Output(c.Colorize().Color("\n[bold]Allocation Resource Utilization[reset]")) c.Ui.Output(formatList(actualResources)) } - if hostStats != nil && stats { - c.Ui.Output(c.Colorize().Color("\n===> [bold]Detailed CPU Stats[reset]")) + if err == nil { + c.Ui.Output(c.Colorize().Color("\n[bold]Host Resource Utilization[reset]")) + c.Ui.Output(formatList(actualResources)) + } + + if hostStats != nil && c.stats { + c.Ui.Output(c.Colorize().Color("\n[bold]CPU Stats[reset]")) c.printCpuStats(hostStats) - c.Ui.Output(c.Colorize().Color("\n===> [bold]Detailed Memory Stats[reset]")) + c.Ui.Output(c.Colorize().Color("\n[bold]Memory Stats[reset]")) c.printMemoryStats(hostStats) - c.Ui.Output(c.Colorize().Color("\n===> [bold]Detailed Disk Stats[reset]")) + c.Ui.Output(c.Colorize().Color("\n[bold]Disk Stats[reset]")) c.printDiskStats(hostStats) } - - allocs, err := getAllocs(client, node, length) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) - return 1 - } - - if len(allocs) > 1 { - c.Ui.Output("\n==> Allocations") - c.Ui.Output(formatList(allocs)) - } - } - if verbose { - // Print the attributes - keys := make([]string, len(node.Attributes)) - for k := range node.Attributes { - keys = append(keys, k) - } - sort.Strings(keys) - - var attributes []string - for _, k := range keys { - if k != "" { - attributes = append(attributes, fmt.Sprintf("%s|%s", k, node.Attributes[k])) - } - } - c.Ui.Output("\n==> Attributes") - c.Ui.Output(formatKV(attributes)) + allocs, err := getAllocs(client, node, c.length) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) + return 1 } + if len(allocs) > 1 { + c.Ui.Output(c.Colorize().Color("\n[bold]Allocations[reset]")) + c.Ui.Output(formatList(allocs)) + } + + if c.verbose { + c.formatAttributes(node) + } if nodeStatsErr != nil { c.Ui.Output("") c.Ui.Error(fmt.Sprintf("error fetching node stats: %v", nodeStatsErr)) } return 0 + +} + +func (c *NodeStatusCommand) formatAttributes(node *api.Node) { + // Print the attributes + keys := make([]string, len(node.Attributes)) + for k := range node.Attributes { + keys = append(keys, k) + } + sort.Strings(keys) + + var attributes []string + for _, k := range keys { + if k != "" { + attributes = append(attributes, fmt.Sprintf("%s|%s", k, node.Attributes[k])) + } + } + c.Ui.Output(c.Colorize().Color("\n[bold]Attributes[reset]")) + c.Ui.Output(formatKV(attributes)) } func (c *NodeStatusCommand) printCpuStats(hostStats *api.HostStats) { - for _, cpuStat := range hostStats.CPU { + l := len(hostStats.CPU) + for i, cpuStat := range hostStats.CPU { cpuStatsAttr := make([]string, 4) cpuStatsAttr[0] = fmt.Sprintf("CPU|%v", cpuStat.CPU) - cpuStatsAttr[1] = fmt.Sprintf("User|%v %%", formatFloat64(cpuStat.User)) - cpuStatsAttr[2] = fmt.Sprintf("System|%v %%", formatFloat64(cpuStat.System)) - cpuStatsAttr[3] = fmt.Sprintf("Idle|%v %%", formatFloat64(cpuStat.Idle)) + cpuStatsAttr[1] = fmt.Sprintf("User|%v%%", humanize.FormatFloat(floatFormat, cpuStat.User)) + cpuStatsAttr[2] = fmt.Sprintf("System|%v%%", humanize.FormatFloat(floatFormat, cpuStat.System)) + cpuStatsAttr[3] = fmt.Sprintf("Idle|%v%%", humanize.FormatFloat(floatFormat, cpuStat.Idle)) c.Ui.Output(formatKV(cpuStatsAttr)) - c.Ui.Output("") + if i+1 < l { + c.Ui.Output("") + } } } @@ -307,17 +331,20 @@ func (c *NodeStatusCommand) printMemoryStats(hostStats *api.HostStats) { } func (c *NodeStatusCommand) printDiskStats(hostStats *api.HostStats) { - for _, diskStat := range hostStats.DiskStats { + l := len(hostStats.DiskStats) + for i, diskStat := range hostStats.DiskStats { diskStatsAttr := make([]string, 7) diskStatsAttr[0] = fmt.Sprintf("Device|%s", diskStat.Device) diskStatsAttr[1] = fmt.Sprintf("MountPoint|%s", diskStat.Mountpoint) diskStatsAttr[2] = fmt.Sprintf("Size|%s", humanize.Bytes(diskStat.Size)) diskStatsAttr[3] = fmt.Sprintf("Used|%s", humanize.Bytes(diskStat.Used)) diskStatsAttr[4] = fmt.Sprintf("Available|%s", humanize.Bytes(diskStat.Available)) - diskStatsAttr[5] = fmt.Sprintf("Used Percent|%v %%", formatFloat64(diskStat.UsedPercent)) - diskStatsAttr[6] = fmt.Sprintf("Inodes Percent|%v %%", formatFloat64(diskStat.InodesUsedPercent)) + diskStatsAttr[5] = fmt.Sprintf("Used Percent|%v%%", humanize.FormatFloat(floatFormat, diskStat.UsedPercent)) + diskStatsAttr[6] = fmt.Sprintf("Inodes Percent|%v%%", humanize.FormatFloat(floatFormat, diskStat.InodesUsedPercent)) c.Ui.Output(formatKV(diskStatsAttr)) - c.Ui.Output("") + if i+1 < l { + c.Ui.Output("") + } } } @@ -428,7 +455,3 @@ func getActualResources(hostStats *api.HostStats, node *api.Node) ([]string, err ) return resources, nil } - -func formatFloat64(val float64) string { - return strconv.FormatFloat(val, 'f', 2, 64) -} diff --git a/demo/vagrant/client1.hcl b/demo/vagrant/client1.hcl index aedce6f8b..c8c311630 100644 --- a/demo/vagrant/client1.hcl +++ b/demo/vagrant/client1.hcl @@ -18,6 +18,9 @@ client { options { "driver.raw_exec.enable" = "1" } + reserved { + cpu = 500 + } } # Modify our port to avoid a collision with server1 diff --git a/website/helpers/command_helpers.rb b/website/helpers/command_helpers.rb index 73e52ee30..451fe50c6 100644 --- a/website/helpers/command_helpers.rb +++ b/website/helpers/command_helpers.rb @@ -8,6 +8,8 @@ module CommandHelpers * `-region=`: The region of the Nomad server to forward commands to. Overrides the `NOMAD_REGION` environment variable if set. Defaults to the Agent's local region. + +* `-no-color`: Disables colored command output. EOF end end From 974c713b4d40f962e458c318407d09093b71ae55 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Sat, 11 Jun 2016 20:55:22 -0700 Subject: [PATCH 2/3] show allocation resources seperate from host --- command/node_status.go | 96 +++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/command/node_status.go b/command/node_status.go index 10e29ab55..922483261 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -236,23 +236,27 @@ func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int { c.Ui.Output(c.Colorize().Color(formatKV(basic))) if !c.short { - allocatedResources, err := getAllocatedResources(client, node) + // Get list of running allocations on the node + runningAllocs, err := getRunningAllocs(client, node.ID) if err != nil { - c.Ui.Error(fmt.Sprintf("Error querying node resources: %s", err)) + c.Ui.Error(fmt.Sprintf("Error querying node for running allocations: %s", err)) return 1 } + + allocatedResources := getAllocatedResources(client, runningAllocs, node) c.Ui.Output(c.Colorize().Color("\n[bold]Allocated Resources[reset]")) c.Ui.Output(formatList(allocatedResources)) - actualResources, err := getActualResources(hostStats, node) + actualResources, err := getActualResources(client, runningAllocs, node) if err == nil { c.Ui.Output(c.Colorize().Color("\n[bold]Allocation Resource Utilization[reset]")) c.Ui.Output(formatList(actualResources)) } + hostResources, err := getHostResources(hostStats, node) if err == nil { c.Ui.Output(c.Colorize().Color("\n[bold]Host Resource Utilization[reset]")) - c.Ui.Output(formatList(actualResources)) + c.Ui.Output(formatList(hostResources)) } if hostStats != nil && c.stats { @@ -384,26 +388,12 @@ func getAllocs(client *api.Client, node *api.Node, length int) ([]string, error) } // getAllocatedResources returns the resource usage of the node. -func getAllocatedResources(client *api.Client, node *api.Node) ([]string, error) { - var resources []string - var cpu, mem, disk, iops int - var totalCpu, totalMem, totalDisk, totalIops int - +func getAllocatedResources(client *api.Client, runningAllocs []*api.Allocation, node *api.Node) []string { // Compute the total - r := node.Resources - res := node.Reserved - if res == nil { - res = &api.Resources{} - } - totalCpu = r.CPU - res.CPU - totalMem = r.MemoryMB - res.MemoryMB - totalDisk = r.DiskMB - res.DiskMB - totalIops = r.IOPS - res.IOPS - - // Get list of running allocations on the node - runningAllocs, err := getRunningAllocs(client, node.ID) + total := computeNodeTotalResources(node) // Get Resources + var cpu, mem, disk, iops int for _, alloc := range runningAllocs { cpu += alloc.Resources.CPU mem += alloc.Resources.MemoryMB @@ -411,23 +401,71 @@ func getAllocatedResources(client *api.Client, node *api.Node) ([]string, error) iops += alloc.Resources.IOPS } - resources = make([]string, 2) + resources := make([]string, 2) resources[0] = "CPU|Memory MB|Disk MB|IOPS" resources[1] = fmt.Sprintf("%v/%v|%v/%v|%v/%v|%v/%v", cpu, - totalCpu, + total.CPU, mem, - totalMem, + total.MemoryMB, disk, - totalDisk, + total.DiskMB, iops, - totalIops) + total.IOPS) - return resources, err + return resources } -// getActualResources returns the actual resource usage of the node. -func getActualResources(hostStats *api.HostStats, node *api.Node) ([]string, error) { +// computeNodeTotalResources returns the total allocatable resources (resources +// minus reserved) +func computeNodeTotalResources(node *api.Node) api.Resources { + total := api.Resources{} + + r := node.Resources + res := node.Reserved + if res == nil { + res = &api.Resources{} + } + total.CPU = r.CPU - res.CPU + total.MemoryMB = r.MemoryMB - res.MemoryMB + total.DiskMB = r.DiskMB - res.DiskMB + total.IOPS = r.IOPS - res.IOPS + return total +} + +// getActualResources returns the actual resource usage of the allocations. +func getActualResources(client *api.Client, runningAllocs []*api.Allocation, node *api.Node) ([]string, error) { + // Compute the total + total := computeNodeTotalResources(node) + + // Get Resources + var cpu float64 + var mem uint64 + for _, alloc := range runningAllocs { + // Make the call to the client to get the actual usage. + stats, err := client.Allocations().Stats(alloc, nil) + if err != nil { + return nil, err + } + + cpu += stats.ResourceUsage.CpuStats.TotalTicks + mem += stats.ResourceUsage.MemoryStats.RSS + } + + // TODO do humanized output for these + resources := make([]string, 2) + resources[0] = "CPU|Memory" + resources[1] = fmt.Sprintf("%v/%v|%v/%v", + math.Floor(cpu), + total.CPU, + humanize.Bytes(mem), + humanize.Bytes(uint64(total.MemoryMB*1024*1024))) + + return resources, nil +} + +// getHostResources returns the actual resource usage of the node. +func getHostResources(hostStats *api.HostStats, node *api.Node) ([]string, error) { if hostStats == nil { return nil, fmt.Errorf("actual resource usage not present") } From a49bb31561c290d96ff1f071c795d96dff7685c4 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Sat, 11 Jun 2016 21:01:53 -0700 Subject: [PATCH 3/3] unify units --- command/node_status.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/command/node_status.go b/command/node_status.go index 922483261..203fe20c6 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -16,6 +16,9 @@ import ( const ( // floatFormat is a format string for formatting floats. floatFormat = "#,###.##" + + // bytesPerMegabyte is the number of bytes per MB + bytesPerMegabyte = 1024 * 1024 ) type NodeStatusCommand struct { @@ -402,14 +405,14 @@ func getAllocatedResources(client *api.Client, runningAllocs []*api.Allocation, } resources := make([]string, 2) - resources[0] = "CPU|Memory MB|Disk MB|IOPS" + resources[0] = "CPU|Memory|Disk|IOPS" resources[1] = fmt.Sprintf("%v/%v|%v/%v|%v/%v|%v/%v", cpu, total.CPU, - mem, - total.MemoryMB, - disk, - total.DiskMB, + humanize.Bytes(uint64(mem*bytesPerMegabyte)), + humanize.Bytes(uint64(total.MemoryMB*bytesPerMegabyte)), + humanize.Bytes(uint64(disk*bytesPerMegabyte)), + humanize.Bytes(uint64(total.DiskMB*bytesPerMegabyte)), iops, total.IOPS) @@ -452,14 +455,13 @@ func getActualResources(client *api.Client, runningAllocs []*api.Allocation, nod mem += stats.ResourceUsage.MemoryStats.RSS } - // TODO do humanized output for these resources := make([]string, 2) resources[0] = "CPU|Memory" resources[1] = fmt.Sprintf("%v/%v|%v/%v", math.Floor(cpu), total.CPU, humanize.Bytes(mem), - humanize.Bytes(uint64(total.MemoryMB*1024*1024))) + humanize.Bytes(uint64(total.MemoryMB*bytesPerMegabyte))) return resources, nil }