github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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  }