github.com/anuvu/nomad@v0.8.7-atom1/command/node_status.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "math" 6 "sort" 7 "strconv" 8 "strings" 9 "time" 10 11 humanize "github.com/dustin/go-humanize" 12 "github.com/posener/complete" 13 14 "github.com/hashicorp/nomad/api" 15 "github.com/hashicorp/nomad/api/contexts" 16 "github.com/hashicorp/nomad/helper" 17 ) 18 19 const ( 20 // floatFormat is a format string for formatting floats. 21 floatFormat = "#,###.##" 22 23 // bytesPerMegabyte is the number of bytes per MB 24 bytesPerMegabyte = 1024 * 1024 25 ) 26 27 type NodeStatusCommand struct { 28 Meta 29 length int 30 short bool 31 verbose bool 32 list_allocs bool 33 self bool 34 stats bool 35 json bool 36 tmpl string 37 } 38 39 func (c *NodeStatusCommand) Help() string { 40 helpText := ` 41 Usage: nomad node status [options] <node> 42 43 Display status information about a given node. The list of nodes 44 returned includes only nodes which jobs may be scheduled to, and 45 includes status and other high-level information. 46 47 If a node ID is passed, information for that specific node will be displayed, 48 including resource usage statistics. If no node ID's are passed, then a 49 short-hand list of all nodes will be displayed. The -self flag is useful to 50 quickly access the status of the local node. 51 52 General Options: 53 54 ` + generalOptionsUsage() + ` 55 56 Node Status Options: 57 58 -self 59 Query the status of the local node. 60 61 -stats 62 Display detailed resource usage statistics. 63 64 -allocs 65 Display a count of running allocations for each node. 66 67 -short 68 Display short output. Used only when a single node is being 69 queried, and drops verbose output about node allocations. 70 71 -verbose 72 Display full information. 73 74 -json 75 Output the node in its JSON format. 76 77 -t 78 Format and display node using a Go template. 79 ` 80 return strings.TrimSpace(helpText) 81 } 82 83 func (c *NodeStatusCommand) Synopsis() string { 84 return "Display status information about nodes" 85 } 86 87 func (c *NodeStatusCommand) AutocompleteFlags() complete.Flags { 88 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 89 complete.Flags{ 90 "-allocs": complete.PredictNothing, 91 "-json": complete.PredictNothing, 92 "-self": complete.PredictNothing, 93 "-short": complete.PredictNothing, 94 "-stats": complete.PredictNothing, 95 "-t": complete.PredictAnything, 96 "-verbose": complete.PredictNothing, 97 }) 98 } 99 100 func (c *NodeStatusCommand) AutocompleteArgs() complete.Predictor { 101 return complete.PredictFunc(func(a complete.Args) []string { 102 client, err := c.Meta.Client() 103 if err != nil { 104 return nil 105 } 106 107 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Nodes, nil) 108 if err != nil { 109 return []string{} 110 } 111 return resp.Matches[contexts.Nodes] 112 }) 113 } 114 115 func (c *NodeStatusCommand) Name() string { return "node-status" } 116 117 func (c *NodeStatusCommand) Run(args []string) int { 118 119 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 120 flags.Usage = func() { c.Ui.Output(c.Help()) } 121 flags.BoolVar(&c.short, "short", false, "") 122 flags.BoolVar(&c.verbose, "verbose", false, "") 123 flags.BoolVar(&c.list_allocs, "allocs", false, "") 124 flags.BoolVar(&c.self, "self", false, "") 125 flags.BoolVar(&c.stats, "stats", false, "") 126 flags.BoolVar(&c.json, "json", false, "") 127 flags.StringVar(&c.tmpl, "t", "", "") 128 129 if err := flags.Parse(args); err != nil { 130 return 1 131 } 132 133 // Check that we got either a single node or none 134 args = flags.Args() 135 if len(args) > 1 { 136 c.Ui.Error("This command takes either one or no arguments") 137 c.Ui.Error(commandErrorText(c)) 138 return 1 139 } 140 141 // Truncate the id unless full length is requested 142 c.length = shortId 143 if c.verbose { 144 c.length = fullId 145 } 146 147 // Get the HTTP client 148 client, err := c.Meta.Client() 149 if err != nil { 150 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 151 return 1 152 } 153 154 // Use list mode if no node name was provided 155 if len(args) == 0 && !c.self { 156 157 // Query the node info 158 nodes, _, err := client.Nodes().List(nil) 159 if err != nil { 160 c.Ui.Error(fmt.Sprintf("Error querying node status: %s", err)) 161 return 1 162 } 163 164 // If output format is specified, format and output the node data list 165 if c.json || len(c.tmpl) > 0 { 166 out, err := Format(c.json, c.tmpl, nodes) 167 if err != nil { 168 c.Ui.Error(err.Error()) 169 return 1 170 } 171 172 c.Ui.Output(out) 173 return 0 174 } 175 176 // Return nothing if no nodes found 177 if len(nodes) == 0 { 178 return 0 179 } 180 181 // Format the nodes list 182 out := make([]string, len(nodes)+1) 183 184 out[0] = "ID|DC|Name|Class|" 185 186 if c.verbose { 187 out[0] += "Address|Version|" 188 } 189 190 out[0] += "Drain|Eligibility|Status" 191 192 if c.list_allocs { 193 out[0] += "|Running Allocs" 194 } 195 196 for i, node := range nodes { 197 out[i+1] = fmt.Sprintf("%s|%s|%s|%s", 198 limit(node.ID, c.length), 199 node.Datacenter, 200 node.Name, 201 node.NodeClass) 202 if c.verbose { 203 out[i+1] += fmt.Sprintf("|%s|%s", 204 node.Address, node.Version) 205 } 206 out[i+1] += fmt.Sprintf("|%v|%s|%s", 207 node.Drain, 208 node.SchedulingEligibility, 209 node.Status) 210 211 if c.list_allocs { 212 numAllocs, err := getRunningAllocs(client, node.ID) 213 if err != nil { 214 c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) 215 return 1 216 } 217 out[i+1] += fmt.Sprintf("|%v", 218 len(numAllocs)) 219 } 220 } 221 222 // Dump the output 223 c.Ui.Output(formatList(out)) 224 return 0 225 } 226 227 // Query the specific node 228 var nodeID string 229 if !c.self { 230 nodeID = args[0] 231 } else { 232 var err error 233 if nodeID, err = getLocalNodeID(client); err != nil { 234 c.Ui.Error(err.Error()) 235 return 1 236 } 237 } 238 if len(nodeID) == 1 { 239 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 240 return 1 241 } 242 243 nodeID = sanitizeUUIDPrefix(nodeID) 244 nodes, _, err := client.Nodes().PrefixList(nodeID) 245 if err != nil { 246 c.Ui.Error(fmt.Sprintf("Error querying node info: %s", err)) 247 return 1 248 } 249 // Return error if no nodes are found 250 if len(nodes) == 0 { 251 c.Ui.Error(fmt.Sprintf("No node(s) with prefix %q found", nodeID)) 252 return 1 253 } 254 if len(nodes) > 1 { 255 // Dump the output 256 c.Ui.Error(fmt.Sprintf("Prefix matched multiple nodes\n\n%s", 257 formatNodeStubList(nodes, c.verbose))) 258 return 1 259 } 260 261 // Prefix lookup matched a single node 262 node, _, err := client.Nodes().Info(nodes[0].ID, nil) 263 if err != nil { 264 c.Ui.Error(fmt.Sprintf("Error querying node info: %s", err)) 265 return 1 266 } 267 268 // If output format is specified, format and output the data 269 if c.json || len(c.tmpl) > 0 { 270 out, err := Format(c.json, c.tmpl, node) 271 if err != nil { 272 c.Ui.Error(err.Error()) 273 return 1 274 } 275 276 c.Ui.Output(out) 277 return 0 278 } 279 280 return c.formatNode(client, node) 281 } 282 283 func nodeDrivers(n *api.Node) []string { 284 var drivers []string 285 for k, v := range n.Attributes { 286 // driver.docker = 1 287 parts := strings.Split(k, ".") 288 if len(parts) != 2 { 289 continue 290 } else if parts[0] != "driver" { 291 continue 292 } else if v != "1" { 293 continue 294 } 295 296 drivers = append(drivers, parts[1]) 297 } 298 299 sort.Strings(drivers) 300 return drivers 301 } 302 303 func formatDrain(n *api.Node) string { 304 if n.DrainStrategy != nil { 305 b := new(strings.Builder) 306 b.WriteString("true") 307 if n.DrainStrategy.DrainSpec.Deadline.Nanoseconds() < 0 { 308 b.WriteString("; force drain") 309 } else if n.DrainStrategy.ForceDeadline.IsZero() { 310 b.WriteString("; no deadline") 311 } else { 312 fmt.Fprintf(b, "; %s deadline", formatTime(n.DrainStrategy.ForceDeadline)) 313 } 314 315 if n.DrainStrategy.IgnoreSystemJobs { 316 b.WriteString("; ignoring system jobs") 317 } 318 return b.String() 319 } 320 321 return strconv.FormatBool(n.Drain) 322 } 323 324 func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int { 325 // Format the header output 326 basic := []string{ 327 fmt.Sprintf("ID|%s", limit(node.ID, c.length)), 328 fmt.Sprintf("Name|%s", node.Name), 329 fmt.Sprintf("Class|%s", node.NodeClass), 330 fmt.Sprintf("DC|%s", node.Datacenter), 331 fmt.Sprintf("Drain|%v", formatDrain(node)), 332 fmt.Sprintf("Eligibility|%s", node.SchedulingEligibility), 333 fmt.Sprintf("Status|%s", node.Status), 334 } 335 336 if c.short { 337 basic = append(basic, fmt.Sprintf("Drivers|%s", strings.Join(nodeDrivers(node), ","))) 338 c.Ui.Output(c.Colorize().Color(formatKV(basic))) 339 } else { 340 // Get the host stats 341 hostStats, nodeStatsErr := client.Nodes().Stats(node.ID, nil) 342 if nodeStatsErr != nil { 343 c.Ui.Output("") 344 c.Ui.Error(fmt.Sprintf("error fetching node stats: %v", nodeStatsErr)) 345 } 346 if hostStats != nil { 347 uptime := time.Duration(hostStats.Uptime * uint64(time.Second)) 348 basic = append(basic, fmt.Sprintf("Uptime|%s", uptime.String())) 349 } 350 351 // Emit the driver info 352 if !c.verbose { 353 driverStatus := fmt.Sprintf("Driver Status| %s", c.outputTruncatedNodeDriverInfo(node)) 354 basic = append(basic, driverStatus) 355 } 356 357 c.Ui.Output(c.Colorize().Color(formatKV(basic))) 358 359 if c.verbose { 360 c.outputNodeDriverInfo(node) 361 } 362 363 // Emit node events 364 c.outputNodeStatusEvents(node) 365 366 // Get list of running allocations on the node 367 runningAllocs, err := getRunningAllocs(client, node.ID) 368 if err != nil { 369 c.Ui.Error(fmt.Sprintf("Error querying node for running allocations: %s", err)) 370 return 1 371 } 372 373 allocatedResources := getAllocatedResources(client, runningAllocs, node) 374 c.Ui.Output(c.Colorize().Color("\n[bold]Allocated Resources[reset]")) 375 c.Ui.Output(formatList(allocatedResources)) 376 377 actualResources, err := getActualResources(client, runningAllocs, node) 378 if err == nil { 379 c.Ui.Output(c.Colorize().Color("\n[bold]Allocation Resource Utilization[reset]")) 380 c.Ui.Output(formatList(actualResources)) 381 } 382 383 hostResources, err := getHostResources(hostStats, node) 384 if err != nil { 385 c.Ui.Output("") 386 c.Ui.Error(fmt.Sprintf("error fetching node stats: %v", err)) 387 } 388 if err == nil { 389 c.Ui.Output(c.Colorize().Color("\n[bold]Host Resource Utilization[reset]")) 390 c.Ui.Output(formatList(hostResources)) 391 } 392 393 if hostStats != nil && c.stats { 394 c.Ui.Output(c.Colorize().Color("\n[bold]CPU Stats[reset]")) 395 c.printCpuStats(hostStats) 396 c.Ui.Output(c.Colorize().Color("\n[bold]Memory Stats[reset]")) 397 c.printMemoryStats(hostStats) 398 c.Ui.Output(c.Colorize().Color("\n[bold]Disk Stats[reset]")) 399 c.printDiskStats(hostStats) 400 } 401 } 402 403 nodeAllocs, _, err := client.Nodes().Allocations(node.ID, nil) 404 if err != nil { 405 c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) 406 return 1 407 } 408 409 c.Ui.Output(c.Colorize().Color("\n[bold]Allocations[reset]")) 410 c.Ui.Output(formatAllocList(nodeAllocs, c.verbose, c.length)) 411 412 if c.verbose { 413 c.formatAttributes(node) 414 c.formatMeta(node) 415 } 416 return 0 417 418 } 419 420 func (c *NodeStatusCommand) outputTruncatedNodeDriverInfo(node *api.Node) string { 421 drivers := make([]string, 0, len(node.Drivers)) 422 423 for driverName, driverInfo := range node.Drivers { 424 if !driverInfo.Detected { 425 continue 426 } 427 428 if !driverInfo.Healthy { 429 drivers = append(drivers, fmt.Sprintf("%s (unhealthy)", driverName)) 430 } else { 431 drivers = append(drivers, driverName) 432 } 433 } 434 sort.Strings(drivers) 435 return strings.Trim(strings.Join(drivers, ","), ", ") 436 } 437 438 func (c *NodeStatusCommand) outputNodeDriverInfo(node *api.Node) { 439 c.Ui.Output(c.Colorize().Color("\n[bold]Drivers")) 440 441 size := len(node.Drivers) 442 nodeDrivers := make([]string, 0, size+1) 443 444 nodeDrivers = append(nodeDrivers, "Driver|Detected|Healthy|Message|Time") 445 446 drivers := make([]string, 0, len(node.Drivers)) 447 for driver := range node.Drivers { 448 drivers = append(drivers, driver) 449 } 450 sort.Strings(drivers) 451 452 for _, driver := range drivers { 453 info := node.Drivers[driver] 454 timestamp := formatTime(info.UpdateTime) 455 nodeDrivers = append(nodeDrivers, fmt.Sprintf("%s|%v|%v|%s|%s", driver, info.Detected, info.Healthy, info.HealthDescription, timestamp)) 456 } 457 c.Ui.Output(formatList(nodeDrivers)) 458 } 459 460 func (c *NodeStatusCommand) outputNodeStatusEvents(node *api.Node) { 461 c.Ui.Output(c.Colorize().Color("\n[bold]Node Events")) 462 c.outputNodeEvent(node.Events) 463 } 464 465 func (c *NodeStatusCommand) outputNodeEvent(events []*api.NodeEvent) { 466 size := len(events) 467 nodeEvents := make([]string, size+1) 468 if c.verbose { 469 nodeEvents[0] = "Time|Subsystem|Message|Details" 470 } else { 471 nodeEvents[0] = "Time|Subsystem|Message" 472 } 473 474 for i, event := range events { 475 timestamp := formatTime(event.Timestamp) 476 subsystem := formatEventSubsystem(event.Subsystem, event.Details["driver"]) 477 msg := event.Message 478 if c.verbose { 479 details := formatEventDetails(event.Details) 480 nodeEvents[size-i] = fmt.Sprintf("%s|%s|%s|%s", timestamp, subsystem, msg, details) 481 } else { 482 nodeEvents[size-i] = fmt.Sprintf("%s|%s|%s", timestamp, subsystem, msg) 483 } 484 } 485 c.Ui.Output(formatList(nodeEvents)) 486 } 487 488 func formatEventSubsystem(subsystem, driverName string) string { 489 if driverName == "" { 490 return subsystem 491 } 492 493 // If this event is for a driver, append the driver name to make the message 494 // clearer 495 return fmt.Sprintf("Driver: %s", driverName) 496 } 497 498 func formatEventDetails(details map[string]string) string { 499 output := make([]string, 0, len(details)) 500 for k, v := range details { 501 output = append(output, fmt.Sprintf("%s: %s ", k, v)) 502 } 503 return strings.Join(output, ", ") 504 } 505 506 func (c *NodeStatusCommand) formatAttributes(node *api.Node) { 507 // Print the attributes 508 keys := make([]string, len(node.Attributes)) 509 for k := range node.Attributes { 510 keys = append(keys, k) 511 } 512 sort.Strings(keys) 513 514 var attributes []string 515 for _, k := range keys { 516 if k != "" { 517 attributes = append(attributes, fmt.Sprintf("%s|%s", k, node.Attributes[k])) 518 } 519 } 520 c.Ui.Output(c.Colorize().Color("\n[bold]Attributes[reset]")) 521 c.Ui.Output(formatKV(attributes)) 522 } 523 524 func (c *NodeStatusCommand) formatMeta(node *api.Node) { 525 // Print the meta 526 keys := make([]string, 0, len(node.Meta)) 527 for k := range node.Meta { 528 keys = append(keys, k) 529 } 530 sort.Strings(keys) 531 532 var meta []string 533 for _, k := range keys { 534 if k != "" { 535 meta = append(meta, fmt.Sprintf("%s|%s", k, node.Meta[k])) 536 } 537 } 538 c.Ui.Output(c.Colorize().Color("\n[bold]Meta[reset]")) 539 c.Ui.Output(formatKV(meta)) 540 } 541 542 func (c *NodeStatusCommand) printCpuStats(hostStats *api.HostStats) { 543 l := len(hostStats.CPU) 544 for i, cpuStat := range hostStats.CPU { 545 cpuStatsAttr := make([]string, 4) 546 cpuStatsAttr[0] = fmt.Sprintf("CPU|%v", cpuStat.CPU) 547 cpuStatsAttr[1] = fmt.Sprintf("User|%v%%", humanize.FormatFloat(floatFormat, cpuStat.User)) 548 cpuStatsAttr[2] = fmt.Sprintf("System|%v%%", humanize.FormatFloat(floatFormat, cpuStat.System)) 549 cpuStatsAttr[3] = fmt.Sprintf("Idle|%v%%", humanize.FormatFloat(floatFormat, cpuStat.Idle)) 550 c.Ui.Output(formatKV(cpuStatsAttr)) 551 if i+1 < l { 552 c.Ui.Output("") 553 } 554 } 555 } 556 557 func (c *NodeStatusCommand) printMemoryStats(hostStats *api.HostStats) { 558 memoryStat := hostStats.Memory 559 memStatsAttr := make([]string, 4) 560 memStatsAttr[0] = fmt.Sprintf("Total|%v", humanize.IBytes(memoryStat.Total)) 561 memStatsAttr[1] = fmt.Sprintf("Available|%v", humanize.IBytes(memoryStat.Available)) 562 memStatsAttr[2] = fmt.Sprintf("Used|%v", humanize.IBytes(memoryStat.Used)) 563 memStatsAttr[3] = fmt.Sprintf("Free|%v", humanize.IBytes(memoryStat.Free)) 564 c.Ui.Output(formatKV(memStatsAttr)) 565 } 566 567 func (c *NodeStatusCommand) printDiskStats(hostStats *api.HostStats) { 568 l := len(hostStats.DiskStats) 569 for i, diskStat := range hostStats.DiskStats { 570 diskStatsAttr := make([]string, 7) 571 diskStatsAttr[0] = fmt.Sprintf("Device|%s", diskStat.Device) 572 diskStatsAttr[1] = fmt.Sprintf("MountPoint|%s", diskStat.Mountpoint) 573 diskStatsAttr[2] = fmt.Sprintf("Size|%s", humanize.IBytes(diskStat.Size)) 574 diskStatsAttr[3] = fmt.Sprintf("Used|%s", humanize.IBytes(diskStat.Used)) 575 diskStatsAttr[4] = fmt.Sprintf("Available|%s", humanize.IBytes(diskStat.Available)) 576 diskStatsAttr[5] = fmt.Sprintf("Used Percent|%v%%", humanize.FormatFloat(floatFormat, diskStat.UsedPercent)) 577 diskStatsAttr[6] = fmt.Sprintf("Inodes Percent|%v%%", humanize.FormatFloat(floatFormat, diskStat.InodesUsedPercent)) 578 c.Ui.Output(formatKV(diskStatsAttr)) 579 if i+1 < l { 580 c.Ui.Output("") 581 } 582 } 583 } 584 585 // getRunningAllocs returns a slice of allocation id's running on the node 586 func getRunningAllocs(client *api.Client, nodeID string) ([]*api.Allocation, error) { 587 var allocs []*api.Allocation 588 589 // Query the node allocations 590 nodeAllocs, _, err := client.Nodes().Allocations(nodeID, nil) 591 // Filter list to only running allocations 592 for _, alloc := range nodeAllocs { 593 if alloc.ClientStatus == "running" { 594 allocs = append(allocs, alloc) 595 } 596 } 597 return allocs, err 598 } 599 600 // getAllocatedResources returns the resource usage of the node. 601 func getAllocatedResources(client *api.Client, runningAllocs []*api.Allocation, node *api.Node) []string { 602 // Compute the total 603 total := computeNodeTotalResources(node) 604 605 // Get Resources 606 var cpu, mem, disk, iops int 607 for _, alloc := range runningAllocs { 608 cpu += *alloc.Resources.CPU 609 mem += *alloc.Resources.MemoryMB 610 disk += *alloc.Resources.DiskMB 611 iops += *alloc.Resources.IOPS 612 } 613 614 resources := make([]string, 2) 615 resources[0] = "CPU|Memory|Disk|IOPS" 616 resources[1] = fmt.Sprintf("%d/%d MHz|%s/%s|%s/%s|%d/%d", 617 cpu, 618 *total.CPU, 619 humanize.IBytes(uint64(mem*bytesPerMegabyte)), 620 humanize.IBytes(uint64(*total.MemoryMB*bytesPerMegabyte)), 621 humanize.IBytes(uint64(disk*bytesPerMegabyte)), 622 humanize.IBytes(uint64(*total.DiskMB*bytesPerMegabyte)), 623 iops, 624 *total.IOPS) 625 626 return resources 627 } 628 629 // computeNodeTotalResources returns the total allocatable resources (resources 630 // minus reserved) 631 func computeNodeTotalResources(node *api.Node) api.Resources { 632 total := api.Resources{} 633 634 r := node.Resources 635 res := node.Reserved 636 if res == nil { 637 res = &api.Resources{} 638 } 639 total.CPU = helper.IntToPtr(*r.CPU - *res.CPU) 640 total.MemoryMB = helper.IntToPtr(*r.MemoryMB - *res.MemoryMB) 641 total.DiskMB = helper.IntToPtr(*r.DiskMB - *res.DiskMB) 642 total.IOPS = helper.IntToPtr(*r.IOPS - *res.IOPS) 643 return total 644 } 645 646 // getActualResources returns the actual resource usage of the allocations. 647 func getActualResources(client *api.Client, runningAllocs []*api.Allocation, node *api.Node) ([]string, error) { 648 // Compute the total 649 total := computeNodeTotalResources(node) 650 651 // Get Resources 652 var cpu float64 653 var mem uint64 654 for _, alloc := range runningAllocs { 655 // Make the call to the client to get the actual usage. 656 stats, err := client.Allocations().Stats(alloc, nil) 657 if err != nil { 658 return nil, err 659 } 660 661 cpu += stats.ResourceUsage.CpuStats.TotalTicks 662 mem += stats.ResourceUsage.MemoryStats.RSS 663 } 664 665 resources := make([]string, 2) 666 resources[0] = "CPU|Memory" 667 resources[1] = fmt.Sprintf("%v/%d MHz|%v/%v", 668 math.Floor(cpu), 669 *total.CPU, 670 humanize.IBytes(mem), 671 humanize.IBytes(uint64(*total.MemoryMB*bytesPerMegabyte))) 672 673 return resources, nil 674 } 675 676 // getHostResources returns the actual resource usage of the node. 677 func getHostResources(hostStats *api.HostStats, node *api.Node) ([]string, error) { 678 if hostStats == nil { 679 return nil, fmt.Errorf("actual resource usage not present") 680 } 681 var resources []string 682 683 // calculate disk usage 684 storageDevice := node.Attributes["unique.storage.volume"] 685 var diskUsed, diskSize uint64 686 var physical bool 687 for _, disk := range hostStats.DiskStats { 688 if disk.Device == storageDevice { 689 diskUsed = disk.Used 690 diskSize = disk.Size 691 physical = true 692 } 693 } 694 695 resources = make([]string, 2) 696 resources[0] = "CPU|Memory|Disk" 697 if physical { 698 resources[1] = fmt.Sprintf("%v/%d MHz|%s/%s|%s/%s", 699 math.Floor(hostStats.CPUTicksConsumed), 700 *node.Resources.CPU, 701 humanize.IBytes(hostStats.Memory.Used), 702 humanize.IBytes(hostStats.Memory.Total), 703 humanize.IBytes(diskUsed), 704 humanize.IBytes(diskSize), 705 ) 706 } else { 707 // If non-physical device are used, output device name only, 708 // since nomad doesn't collect the stats data. 709 resources[1] = fmt.Sprintf("%v/%d MHz|%s/%s|(%s)", 710 math.Floor(hostStats.CPUTicksConsumed), 711 *node.Resources.CPU, 712 humanize.IBytes(hostStats.Memory.Used), 713 humanize.IBytes(hostStats.Memory.Total), 714 storageDevice, 715 ) 716 } 717 return resources, nil 718 } 719 720 // formatNodeStubList is used to return a table format of a list of node stubs. 721 func formatNodeStubList(nodes []*api.NodeListStub, verbose bool) string { 722 // Return error if no nodes are found 723 if len(nodes) == 0 { 724 return "" 725 } 726 // Truncate the id unless full length is requested 727 length := shortId 728 if verbose { 729 length = fullId 730 } 731 732 // Format the nodes list that matches the prefix so that the user 733 // can create a more specific request 734 out := make([]string, len(nodes)+1) 735 out[0] = "ID|DC|Name|Class|Drain|Eligibility|Status" 736 for i, node := range nodes { 737 out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s|%s", 738 limit(node.ID, length), 739 node.Datacenter, 740 node.Name, 741 node.NodeClass, 742 node.Drain, 743 node.SchedulingEligibility, 744 node.Status) 745 } 746 747 return formatList(out) 748 }