github.com/thomasobenaus/nomad@v0.11.1/command/alloc_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/hashicorp/nomad/api" 13 "github.com/hashicorp/nomad/api/contexts" 14 "github.com/hashicorp/nomad/client/allocrunner/taskrunner/restarts" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/posener/complete" 17 ) 18 19 type AllocStatusCommand struct { 20 Meta 21 } 22 23 func (c *AllocStatusCommand) Help() string { 24 helpText := ` 25 Usage: nomad alloc status [options] <allocation> 26 27 Display information about existing allocations and its tasks. This command can 28 be used to inspect the current status of an allocation, including its running 29 status, metadata, and verbose failure messages reported by internal 30 subsystems. 31 32 General Options: 33 34 ` + generalOptionsUsage() + ` 35 36 Alloc Status Options: 37 38 -short 39 Display short output. Shows only the most recent task event. 40 41 -stats 42 Display detailed resource usage statistics. 43 44 -verbose 45 Show full information. 46 47 -json 48 Output the allocation in its JSON format. 49 50 -t 51 Format and display allocation using a Go template. 52 ` 53 54 return strings.TrimSpace(helpText) 55 } 56 57 func (c *AllocStatusCommand) Synopsis() string { 58 return "Display allocation status information and metadata" 59 } 60 61 func (c *AllocStatusCommand) AutocompleteFlags() complete.Flags { 62 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 63 complete.Flags{ 64 "-short": complete.PredictNothing, 65 "-verbose": complete.PredictNothing, 66 "-json": complete.PredictNothing, 67 "-t": complete.PredictAnything, 68 }) 69 } 70 71 func (c *AllocStatusCommand) AutocompleteArgs() complete.Predictor { 72 return complete.PredictFunc(func(a complete.Args) []string { 73 client, err := c.Meta.Client() 74 if err != nil { 75 return nil 76 } 77 78 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Allocs, nil) 79 if err != nil { 80 return []string{} 81 } 82 return resp.Matches[contexts.Allocs] 83 }) 84 } 85 86 func (c *AllocStatusCommand) Name() string { return "alloc status" } 87 88 func (c *AllocStatusCommand) Run(args []string) int { 89 var short, displayStats, verbose, json bool 90 var tmpl string 91 92 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 93 flags.Usage = func() { c.Ui.Output(c.Help()) } 94 flags.BoolVar(&short, "short", false, "") 95 flags.BoolVar(&verbose, "verbose", false, "") 96 flags.BoolVar(&displayStats, "stats", false, "") 97 flags.BoolVar(&json, "json", false, "") 98 flags.StringVar(&tmpl, "t", "", "") 99 100 if err := flags.Parse(args); err != nil { 101 return 1 102 } 103 104 // Check that we got exactly one allocation ID 105 args = flags.Args() 106 107 // Get the HTTP client 108 client, err := c.Meta.Client() 109 if err != nil { 110 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 111 return 1 112 } 113 114 // If args not specified but output format is specified, format and output the allocations data list 115 if len(args) == 0 && json || len(tmpl) > 0 { 116 allocs, _, err := client.Allocations().List(nil) 117 if err != nil { 118 c.Ui.Error(fmt.Sprintf("Error querying allocations: %v", err)) 119 return 1 120 } 121 122 out, err := Format(json, tmpl, allocs) 123 if err != nil { 124 c.Ui.Error(err.Error()) 125 return 1 126 } 127 128 c.Ui.Output(out) 129 return 0 130 } 131 132 if len(args) != 1 { 133 c.Ui.Error("This command takes one of the following argument conditions:") 134 c.Ui.Error(" * A single <allocation>") 135 c.Ui.Error(" * No arguments, with output format specified") 136 c.Ui.Error(commandErrorText(c)) 137 return 1 138 } 139 allocID := args[0] 140 141 // Truncate the id unless full length is requested 142 length := shortId 143 if verbose { 144 length = fullId 145 } 146 147 // Query the allocation info 148 if len(allocID) == 1 { 149 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 150 return 1 151 } 152 153 allocID = sanitizeUUIDPrefix(allocID) 154 allocs, _, err := client.Allocations().PrefixList(allocID) 155 if err != nil { 156 c.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) 157 return 1 158 } 159 if len(allocs) == 0 { 160 c.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) 161 return 1 162 } 163 if len(allocs) > 1 { 164 out := formatAllocListStubs(allocs, verbose, length) 165 c.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", out)) 166 return 0 167 } 168 // Prefix lookup matched a single allocation 169 alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) 170 if err != nil { 171 c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) 172 return 1 173 } 174 175 // If output format is specified, format and output the data 176 if json || len(tmpl) > 0 { 177 out, err := Format(json, tmpl, alloc) 178 if err != nil { 179 c.Ui.Error(err.Error()) 180 return 1 181 } 182 183 c.Ui.Output(out) 184 return 0 185 } 186 187 // Format the allocation data 188 if short { 189 c.Ui.Output(formatAllocShortInfo(alloc, client)) 190 } else { 191 output, err := formatAllocBasicInfo(alloc, client, length, verbose) 192 if err != nil { 193 c.Ui.Error(err.Error()) 194 return 1 195 } 196 c.Ui.Output(output) 197 198 if alloc.AllocatedResources != nil && len(alloc.AllocatedResources.Shared.Networks) > 0 && alloc.AllocatedResources.Shared.Networks[0].HasPorts() { 199 c.Ui.Output("") 200 c.Ui.Output(formatAllocNetworkInfo(alloc)) 201 } 202 } 203 204 if short { 205 c.shortTaskStatus(alloc) 206 } else { 207 var statsErr error 208 var stats *api.AllocResourceUsage 209 stats, statsErr = client.Allocations().Stats(alloc, nil) 210 if statsErr != nil { 211 c.Ui.Output("") 212 if statsErr != api.NodeDownErr { 213 c.Ui.Error(fmt.Sprintf("Couldn't retrieve stats: %v", statsErr)) 214 } else { 215 c.Ui.Output("Omitting resource statistics since the node is down.") 216 } 217 } 218 c.outputTaskDetails(alloc, stats, displayStats, verbose) 219 } 220 221 // Format the detailed status 222 if verbose { 223 c.Ui.Output(c.Colorize().Color("\n[bold]Placement Metrics[reset]")) 224 c.Ui.Output(formatAllocMetrics(alloc.Metrics, true, " ")) 225 } 226 227 return 0 228 } 229 230 func formatAllocShortInfo(alloc *api.Allocation, client *api.Client) string { 231 formattedCreateTime := prettyTimeDiff(time.Unix(0, alloc.CreateTime), time.Now()) 232 formattedModifyTime := prettyTimeDiff(time.Unix(0, alloc.ModifyTime), time.Now()) 233 234 basic := []string{ 235 fmt.Sprintf("ID|%s", alloc.ID), 236 fmt.Sprintf("Name|%s", alloc.Name), 237 fmt.Sprintf("Created|%s", formattedCreateTime), 238 fmt.Sprintf("Modified|%s", formattedModifyTime), 239 } 240 241 return formatKV(basic) 242 } 243 244 func formatAllocBasicInfo(alloc *api.Allocation, client *api.Client, uuidLength int, verbose bool) (string, error) { 245 var formattedCreateTime, formattedModifyTime string 246 247 if verbose { 248 formattedCreateTime = formatUnixNanoTime(alloc.CreateTime) 249 formattedModifyTime = formatUnixNanoTime(alloc.ModifyTime) 250 } else { 251 formattedCreateTime = prettyTimeDiff(time.Unix(0, alloc.CreateTime), time.Now()) 252 formattedModifyTime = prettyTimeDiff(time.Unix(0, alloc.ModifyTime), time.Now()) 253 } 254 255 basic := []string{ 256 fmt.Sprintf("ID|%s", alloc.ID), 257 fmt.Sprintf("Eval ID|%s", limit(alloc.EvalID, uuidLength)), 258 fmt.Sprintf("Name|%s", alloc.Name), 259 fmt.Sprintf("Node ID|%s", limit(alloc.NodeID, uuidLength)), 260 fmt.Sprintf("Node Name|%s", alloc.NodeName), 261 fmt.Sprintf("Job ID|%s", alloc.JobID), 262 fmt.Sprintf("Job Version|%d", *alloc.Job.Version), 263 fmt.Sprintf("Client Status|%s", alloc.ClientStatus), 264 fmt.Sprintf("Client Description|%s", alloc.ClientDescription), 265 fmt.Sprintf("Desired Status|%s", alloc.DesiredStatus), 266 fmt.Sprintf("Desired Description|%s", alloc.DesiredDescription), 267 fmt.Sprintf("Created|%s", formattedCreateTime), 268 fmt.Sprintf("Modified|%s", formattedModifyTime), 269 } 270 271 if alloc.DeploymentID != "" { 272 health := "unset" 273 canary := false 274 if alloc.DeploymentStatus != nil { 275 if alloc.DeploymentStatus.Healthy != nil { 276 if *alloc.DeploymentStatus.Healthy { 277 health = "healthy" 278 } else { 279 health = "unhealthy" 280 } 281 } 282 283 canary = alloc.DeploymentStatus.Canary 284 } 285 286 basic = append(basic, 287 fmt.Sprintf("Deployment ID|%s", limit(alloc.DeploymentID, uuidLength)), 288 fmt.Sprintf("Deployment Health|%s", health)) 289 if canary { 290 basic = append(basic, fmt.Sprintf("Canary|%v", true)) 291 } 292 } 293 294 if alloc.RescheduleTracker != nil && len(alloc.RescheduleTracker.Events) > 0 { 295 attempts, total := alloc.RescheduleInfo(time.Unix(0, alloc.ModifyTime)) 296 // Show this section only if the reschedule policy limits the number of attempts 297 if total > 0 { 298 reschedInfo := fmt.Sprintf("Reschedule Attempts|%d/%d", attempts, total) 299 basic = append(basic, reschedInfo) 300 } 301 } 302 if alloc.NextAllocation != "" { 303 basic = append(basic, 304 fmt.Sprintf("Replacement Alloc ID|%s", limit(alloc.NextAllocation, uuidLength))) 305 } 306 if alloc.FollowupEvalID != "" { 307 nextEvalTime := futureEvalTimePretty(alloc.FollowupEvalID, client) 308 if nextEvalTime != "" { 309 basic = append(basic, 310 fmt.Sprintf("Reschedule Eligibility|%s", nextEvalTime)) 311 } 312 } 313 314 if verbose { 315 basic = append(basic, 316 fmt.Sprintf("Evaluated Nodes|%d", alloc.Metrics.NodesEvaluated), 317 fmt.Sprintf("Filtered Nodes|%d", alloc.Metrics.NodesFiltered), 318 fmt.Sprintf("Exhausted Nodes|%d", alloc.Metrics.NodesExhausted), 319 fmt.Sprintf("Allocation Time|%s", alloc.Metrics.AllocationTime), 320 fmt.Sprintf("Failures|%d", alloc.Metrics.CoalescedFailures)) 321 } 322 323 return formatKV(basic), nil 324 } 325 326 func formatAllocNetworkInfo(alloc *api.Allocation) string { 327 nw := alloc.AllocatedResources.Shared.Networks[0] 328 addrs := make([]string, len(nw.DynamicPorts)+len(nw.ReservedPorts)+1) 329 addrs[0] = "Label|Dynamic|Address" 330 portFmt := func(port *api.Port, dyn string) string { 331 s := fmt.Sprintf("%s|%s|%s:%d", port.Label, dyn, nw.IP, port.Value) 332 if port.To > 0 { 333 s += fmt.Sprintf(" -> %d", port.To) 334 } 335 return s 336 } 337 for idx, port := range nw.DynamicPorts { 338 addrs[idx+1] = portFmt(&port, "yes") 339 } 340 for idx, port := range nw.ReservedPorts { 341 addrs[idx+1+len(nw.DynamicPorts)] = portFmt(&port, "yes") 342 } 343 344 var mode string 345 if nw.Mode != "" { 346 mode = fmt.Sprintf(" (mode = %q)", nw.Mode) 347 } 348 349 return fmt.Sprintf("Allocation Addresses%s\n%s", mode, formatList(addrs)) 350 } 351 352 // futureEvalTimePretty returns when the eval is eligible to reschedule 353 // relative to current time, based on the WaitUntil field 354 func futureEvalTimePretty(evalID string, client *api.Client) string { 355 evaluation, _, err := client.Evaluations().Info(evalID, nil) 356 // Eval time is not a critical output, 357 // don't return it on errors, if its not set or already in the past 358 if err != nil || evaluation.WaitUntil.IsZero() || time.Now().After(evaluation.WaitUntil) { 359 return "" 360 } 361 return prettyTimeDiff(evaluation.WaitUntil, time.Now()) 362 } 363 364 // outputTaskDetails prints task details for each task in the allocation, 365 // optionally printing verbose statistics if displayStats is set 366 func (c *AllocStatusCommand) outputTaskDetails(alloc *api.Allocation, stats *api.AllocResourceUsage, displayStats bool, verbose bool) { 367 taskLifecycles := map[string]*api.TaskLifecycle{} 368 for _, t := range alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks { 369 taskLifecycles[t.Name] = t.Lifecycle 370 } 371 372 for _, task := range c.sortedTaskStateIterator(alloc.TaskStates, taskLifecycles) { 373 state := alloc.TaskStates[task] 374 375 lcIndicator := "" 376 if lc := taskLifecycles[task]; !lc.Empty() { 377 lcIndicator = " (" + lifecycleDisplayName(lc) + ")" 378 } 379 380 c.Ui.Output(c.Colorize().Color(fmt.Sprintf("\n[bold]Task %q%v is %q[reset]", task, lcIndicator, state.State))) 381 c.outputTaskResources(alloc, task, stats, displayStats) 382 c.Ui.Output("") 383 c.outputTaskVolumes(alloc, task, verbose) 384 c.outputTaskStatus(state) 385 } 386 } 387 388 func formatTaskTimes(t time.Time) string { 389 if t.IsZero() { 390 return "N/A" 391 } 392 393 return formatTime(t) 394 } 395 396 // outputTaskStatus prints out a list of the most recent events for the given 397 // task state. 398 func (c *AllocStatusCommand) outputTaskStatus(state *api.TaskState) { 399 basic := []string{ 400 fmt.Sprintf("Started At|%s", formatTaskTimes(state.StartedAt)), 401 fmt.Sprintf("Finished At|%s", formatTaskTimes(state.FinishedAt)), 402 fmt.Sprintf("Total Restarts|%d", state.Restarts), 403 fmt.Sprintf("Last Restart|%s", formatTaskTimes(state.LastRestart))} 404 405 c.Ui.Output("Task Events:") 406 c.Ui.Output(formatKV(basic)) 407 c.Ui.Output("") 408 409 c.Ui.Output("Recent Events:") 410 events := make([]string, len(state.Events)+1) 411 events[0] = "Time|Type|Description" 412 413 size := len(state.Events) 414 for i, event := range state.Events { 415 msg := event.DisplayMessage 416 if msg == "" { 417 msg = buildDisplayMessage(event) 418 } 419 formattedTime := formatUnixNanoTime(event.Time) 420 events[size-i] = fmt.Sprintf("%s|%s|%s", formattedTime, event.Type, msg) 421 // Reverse order so we are sorted by time 422 } 423 c.Ui.Output(formatList(events)) 424 } 425 426 func buildDisplayMessage(event *api.TaskEvent) string { 427 // Build up the description based on the event type. 428 var desc string 429 switch event.Type { 430 case api.TaskSetup: 431 desc = event.Message 432 case api.TaskStarted: 433 desc = "Task started by client" 434 case api.TaskReceived: 435 desc = "Task received by client" 436 case api.TaskFailedValidation: 437 if event.ValidationError != "" { 438 desc = event.ValidationError 439 } else { 440 desc = "Validation of task failed" 441 } 442 case api.TaskSetupFailure: 443 if event.SetupError != "" { 444 desc = event.SetupError 445 } else { 446 desc = "Task setup failed" 447 } 448 case api.TaskDriverFailure: 449 if event.DriverError != "" { 450 desc = event.DriverError 451 } else { 452 desc = "Failed to start task" 453 } 454 case api.TaskDownloadingArtifacts: 455 desc = "Client is downloading artifacts" 456 case api.TaskArtifactDownloadFailed: 457 if event.DownloadError != "" { 458 desc = event.DownloadError 459 } else { 460 desc = "Failed to download artifacts" 461 } 462 case api.TaskKilling: 463 if event.KillReason != "" { 464 desc = fmt.Sprintf("Killing task: %v", event.KillReason) 465 } else if event.KillTimeout != 0 { 466 desc = fmt.Sprintf("Sent interrupt. Waiting %v before force killing", event.KillTimeout) 467 } else { 468 desc = "Sent interrupt" 469 } 470 case api.TaskKilled: 471 if event.KillError != "" { 472 desc = event.KillError 473 } else { 474 desc = "Task successfully killed" 475 } 476 case api.TaskTerminated: 477 var parts []string 478 parts = append(parts, fmt.Sprintf("Exit Code: %d", event.ExitCode)) 479 480 if event.Signal != 0 { 481 parts = append(parts, fmt.Sprintf("Signal: %d", event.Signal)) 482 } 483 484 if event.Message != "" { 485 parts = append(parts, fmt.Sprintf("Exit Message: %q", event.Message)) 486 } 487 desc = strings.Join(parts, ", ") 488 case api.TaskRestarting: 489 in := fmt.Sprintf("Task restarting in %v", time.Duration(event.StartDelay)) 490 if event.RestartReason != "" && event.RestartReason != restarts.ReasonWithinPolicy { 491 desc = fmt.Sprintf("%s - %s", event.RestartReason, in) 492 } else { 493 desc = in 494 } 495 case api.TaskNotRestarting: 496 if event.RestartReason != "" { 497 desc = event.RestartReason 498 } else { 499 desc = "Task exceeded restart policy" 500 } 501 case api.TaskSiblingFailed: 502 if event.FailedSibling != "" { 503 desc = fmt.Sprintf("Task's sibling %q failed", event.FailedSibling) 504 } else { 505 desc = "Task's sibling failed" 506 } 507 case api.TaskSignaling: 508 sig := event.TaskSignal 509 reason := event.TaskSignalReason 510 511 if sig == "" && reason == "" { 512 desc = "Task being sent a signal" 513 } else if sig == "" { 514 desc = reason 515 } else if reason == "" { 516 desc = fmt.Sprintf("Task being sent signal %v", sig) 517 } else { 518 desc = fmt.Sprintf("Task being sent signal %v: %v", sig, reason) 519 } 520 case api.TaskRestartSignal: 521 if event.RestartReason != "" { 522 desc = event.RestartReason 523 } else { 524 desc = "Task signaled to restart" 525 } 526 case api.TaskDriverMessage: 527 desc = event.DriverMessage 528 case api.TaskLeaderDead: 529 desc = "Leader Task in Group dead" 530 default: 531 desc = event.Message 532 } 533 534 return desc 535 } 536 537 // outputTaskResources prints the task resources for the passed task and if 538 // displayStats is set, verbose resource usage statistics 539 func (c *AllocStatusCommand) outputTaskResources(alloc *api.Allocation, task string, stats *api.AllocResourceUsage, displayStats bool) { 540 resource, ok := alloc.TaskResources[task] 541 if !ok { 542 return 543 } 544 545 c.Ui.Output("Task Resources") 546 var addr []string 547 for _, nw := range resource.Networks { 548 ports := append(nw.DynamicPorts, nw.ReservedPorts...) 549 for _, port := range ports { 550 addr = append(addr, fmt.Sprintf("%v: %v:%v\n", port.Label, nw.IP, port.Value)) 551 } 552 } 553 554 var resourcesOutput []string 555 resourcesOutput = append(resourcesOutput, "CPU|Memory|Disk|Addresses") 556 firstAddr := "" 557 if len(addr) > 0 { 558 firstAddr = addr[0] 559 } 560 561 // Display the rolled up stats. If possible prefer the live statistics 562 cpuUsage := strconv.Itoa(*resource.CPU) 563 memUsage := humanize.IBytes(uint64(*resource.MemoryMB * bytesPerMegabyte)) 564 var deviceStats []*api.DeviceGroupStats 565 566 if stats != nil { 567 if ru, ok := stats.Tasks[task]; ok && ru != nil && ru.ResourceUsage != nil { 568 if cs := ru.ResourceUsage.CpuStats; cs != nil { 569 cpuUsage = fmt.Sprintf("%v/%v", math.Floor(cs.TotalTicks), cpuUsage) 570 } 571 if ms := ru.ResourceUsage.MemoryStats; ms != nil { 572 memUsage = fmt.Sprintf("%v/%v", humanize.IBytes(ms.RSS), memUsage) 573 } 574 deviceStats = ru.ResourceUsage.DeviceStats 575 } 576 } 577 resourcesOutput = append(resourcesOutput, fmt.Sprintf("%v MHz|%v|%v|%v", 578 cpuUsage, 579 memUsage, 580 humanize.IBytes(uint64(*alloc.Resources.DiskMB*bytesPerMegabyte)), 581 firstAddr)) 582 for i := 1; i < len(addr); i++ { 583 resourcesOutput = append(resourcesOutput, fmt.Sprintf("||||%v", addr[i])) 584 } 585 c.Ui.Output(formatListWithSpaces(resourcesOutput)) 586 587 if len(deviceStats) > 0 { 588 c.Ui.Output("") 589 c.Ui.Output("Device Stats") 590 c.Ui.Output(formatList(getDeviceResources(deviceStats))) 591 } 592 593 if stats != nil { 594 if ru, ok := stats.Tasks[task]; ok && ru != nil && displayStats && ru.ResourceUsage != nil { 595 c.Ui.Output("") 596 c.outputVerboseResourceUsage(task, ru.ResourceUsage) 597 } 598 } 599 } 600 601 // outputVerboseResourceUsage outputs the verbose resource usage for the passed 602 // task 603 func (c *AllocStatusCommand) outputVerboseResourceUsage(task string, resourceUsage *api.ResourceUsage) { 604 memoryStats := resourceUsage.MemoryStats 605 cpuStats := resourceUsage.CpuStats 606 deviceStats := resourceUsage.DeviceStats 607 608 if memoryStats != nil && len(memoryStats.Measured) > 0 { 609 c.Ui.Output("Memory Stats") 610 611 // Sort the measured stats 612 sort.Strings(memoryStats.Measured) 613 614 var measuredStats []string 615 for _, measured := range memoryStats.Measured { 616 switch measured { 617 case "RSS": 618 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.RSS)) 619 case "Cache": 620 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.Cache)) 621 case "Swap": 622 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.Swap)) 623 case "Usage": 624 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.Usage)) 625 case "Max Usage": 626 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.MaxUsage)) 627 case "Kernel Usage": 628 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.KernelUsage)) 629 case "Kernel Max Usage": 630 measuredStats = append(measuredStats, humanize.IBytes(memoryStats.KernelMaxUsage)) 631 } 632 } 633 634 out := make([]string, 2) 635 out[0] = strings.Join(memoryStats.Measured, "|") 636 out[1] = strings.Join(measuredStats, "|") 637 c.Ui.Output(formatList(out)) 638 c.Ui.Output("") 639 } 640 641 if cpuStats != nil && len(cpuStats.Measured) > 0 { 642 c.Ui.Output("CPU Stats") 643 644 // Sort the measured stats 645 sort.Strings(cpuStats.Measured) 646 647 var measuredStats []string 648 for _, measured := range cpuStats.Measured { 649 switch measured { 650 case "Percent": 651 percent := strconv.FormatFloat(cpuStats.Percent, 'f', 2, 64) 652 measuredStats = append(measuredStats, fmt.Sprintf("%v%%", percent)) 653 case "Throttled Periods": 654 measuredStats = append(measuredStats, fmt.Sprintf("%v", cpuStats.ThrottledPeriods)) 655 case "Throttled Time": 656 measuredStats = append(measuredStats, fmt.Sprintf("%v", cpuStats.ThrottledTime)) 657 case "User Mode": 658 percent := strconv.FormatFloat(cpuStats.UserMode, 'f', 2, 64) 659 measuredStats = append(measuredStats, fmt.Sprintf("%v%%", percent)) 660 case "System Mode": 661 percent := strconv.FormatFloat(cpuStats.SystemMode, 'f', 2, 64) 662 measuredStats = append(measuredStats, fmt.Sprintf("%v%%", percent)) 663 } 664 } 665 666 out := make([]string, 2) 667 out[0] = strings.Join(cpuStats.Measured, "|") 668 out[1] = strings.Join(measuredStats, "|") 669 c.Ui.Output(formatList(out)) 670 } 671 672 if len(deviceStats) > 0 { 673 c.Ui.Output("") 674 c.Ui.Output("Device Stats") 675 676 printDeviceStats(c.Ui, deviceStats) 677 } 678 } 679 680 // shortTaskStatus prints out the current state of each task. 681 func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) { 682 tasks := make([]string, 0, len(alloc.TaskStates)+1) 683 tasks = append(tasks, "Name|State|Last Event|Time|Lifecycle") 684 685 taskLifecycles := map[string]*api.TaskLifecycle{} 686 for _, t := range alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks { 687 taskLifecycles[t.Name] = t.Lifecycle 688 } 689 690 for _, task := range c.sortedTaskStateIterator(alloc.TaskStates, taskLifecycles) { 691 state := alloc.TaskStates[task] 692 lastState := state.State 693 var lastEvent, lastTime string 694 695 l := len(state.Events) 696 if l != 0 { 697 last := state.Events[l-1] 698 lastEvent = last.Type 699 lastTime = formatUnixNanoTime(last.Time) 700 } 701 702 tasks = append(tasks, fmt.Sprintf("%s|%s|%s|%s|%s", 703 task, lastState, lastEvent, lastTime, lifecycleDisplayName(taskLifecycles[task]))) 704 } 705 706 c.Ui.Output(c.Colorize().Color("\n[bold]Tasks[reset]")) 707 c.Ui.Output(formatList(tasks)) 708 } 709 710 // sortedTaskStateIterator is a helper that takes the task state map and returns a 711 // channel that returns the keys in a sorted order. 712 func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState, lifecycles map[string]*api.TaskLifecycle) []string { 713 keys := make([]string, len(m)) 714 i := 0 715 for k := range m { 716 keys[i] = k 717 i++ 718 } 719 sort.Strings(keys) 720 721 // display prestart then prestart sidecar then main 722 sort.SliceStable(keys, func(i, j int) bool { 723 lci := lifecycles[keys[i]] 724 lcj := lifecycles[keys[j]] 725 726 switch { 727 case lci == nil: 728 return false 729 case lcj == nil: 730 return true 731 case !lci.Sidecar && lcj.Sidecar: 732 return true 733 default: 734 return false 735 } 736 }) 737 738 return keys 739 } 740 741 func lifecycleDisplayName(l *api.TaskLifecycle) string { 742 if l.Empty() { 743 return "main" 744 } 745 746 sidecar := "" 747 if l.Sidecar { 748 sidecar = " sidecar" 749 } 750 return l.Hook + sidecar 751 } 752 753 func (c *AllocStatusCommand) outputTaskVolumes(alloc *api.Allocation, taskName string, verbose bool) { 754 var task *api.Task 755 var tg *api.TaskGroup 756 FOUND: 757 for _, tg = range alloc.Job.TaskGroups { 758 for _, task = range tg.Tasks { 759 if task.Name == taskName { 760 break FOUND 761 } 762 } 763 } 764 if task == nil || tg == nil { 765 c.Ui.Error(fmt.Sprintf("Could not find task data for %q", taskName)) 766 return 767 } 768 if len(task.VolumeMounts) == 0 { 769 return 770 } 771 client, err := c.Meta.Client() 772 if err != nil { 773 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 774 return 775 } 776 777 var hostVolumesOutput []string 778 var csiVolumesOutput []string 779 hostVolumesOutput = append(hostVolumesOutput, "ID|Read Only") 780 if verbose { 781 csiVolumesOutput = append(csiVolumesOutput, 782 "ID|Plugin|Provider|Schedulable|Read Only|Mount Options") 783 } else { 784 csiVolumesOutput = append(csiVolumesOutput, "ID|Read Only") 785 } 786 787 for _, volMount := range task.VolumeMounts { 788 volReq := tg.Volumes[*volMount.Volume] 789 switch volReq.Type { 790 case structs.VolumeTypeHost: 791 hostVolumesOutput = append(hostVolumesOutput, 792 fmt.Sprintf("%s|%v", volReq.Name, *volMount.ReadOnly)) 793 case structs.VolumeTypeCSI: 794 if verbose { 795 // there's an extra API call per volume here so we toggle it 796 // off with the -verbose flag 797 vol, _, err := client.CSIVolumes().Info(volReq.Name, nil) 798 if err != nil { 799 c.Ui.Error(fmt.Sprintf("Error retrieving volume info for %q: %s", 800 volReq.Name, err)) 801 continue 802 } 803 csiVolumesOutput = append(csiVolumesOutput, 804 fmt.Sprintf("%s|%s|%s|%v|%v|%s", 805 volReq.Name, 806 vol.PluginID, 807 vol.Provider, 808 vol.Schedulable, 809 volReq.ReadOnly, 810 csiVolMountOption(vol.MountOptions, volReq.MountOptions), 811 )) 812 } else { 813 csiVolumesOutput = append(csiVolumesOutput, 814 fmt.Sprintf("%s|%v", volReq.Name, volReq.ReadOnly)) 815 } 816 } 817 } 818 if len(hostVolumesOutput) > 1 { 819 c.Ui.Output("Host Volumes:") 820 c.Ui.Output(formatList(hostVolumesOutput)) 821 c.Ui.Output("") // line padding to next stanza 822 } 823 if len(csiVolumesOutput) > 1 { 824 c.Ui.Output("CSI Volumes:") 825 c.Ui.Output(formatList(csiVolumesOutput)) 826 c.Ui.Output("") // line padding to next stanza 827 } 828 }