2023-04-10 15:36:59 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2015-09-16 20:58:33 +00:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2019-11-01 17:58:22 +00:00
|
|
|
"sort"
|
2016-05-31 21:51:23 +00:00
|
|
|
"strings"
|
2015-09-16 20:58:33 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-09-18 17:03:23 +00:00
|
|
|
// updateWait is the amount of time to wait between status
|
|
|
|
// updates. Because the monitor is poll-based, we use this
|
|
|
|
// delay to avoid overwhelming the API server.
|
|
|
|
updateWait = time.Second
|
2015-09-16 20:58:33 +00:00
|
|
|
)
|
|
|
|
|
2015-09-18 17:03:23 +00:00
|
|
|
// evalState is used to store the current "state of the world"
|
|
|
|
// in the context of monitoring an evaluation.
|
|
|
|
type evalState struct {
|
2017-07-07 02:55:58 +00:00
|
|
|
status string
|
|
|
|
desc string
|
|
|
|
node string
|
|
|
|
deployment string
|
|
|
|
job string
|
|
|
|
allocs map[string]*allocState
|
|
|
|
wait time.Duration
|
|
|
|
index uint64
|
2015-09-18 17:03:23 +00:00
|
|
|
}
|
|
|
|
|
2015-09-22 17:27:30 +00:00
|
|
|
// newEvalState creates and initializes a new monitorState
|
|
|
|
func newEvalState() *evalState {
|
|
|
|
return &evalState{
|
2022-08-02 14:33:08 +00:00
|
|
|
status: api.EvalStatusPending,
|
2015-09-22 17:27:30 +00:00
|
|
|
allocs: make(map[string]*allocState),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-18 17:03:23 +00:00
|
|
|
// allocState is used to track the state of an allocation
|
|
|
|
type allocState struct {
|
|
|
|
id string
|
|
|
|
group string
|
|
|
|
node string
|
|
|
|
desired string
|
|
|
|
desiredDesc string
|
|
|
|
client string
|
2015-09-21 22:55:10 +00:00
|
|
|
clientDesc string
|
2015-09-18 17:03:23 +00:00
|
|
|
index uint64
|
|
|
|
}
|
|
|
|
|
2015-09-16 20:58:33 +00:00
|
|
|
// monitor wraps an evaluation monitor and holds metadata and
|
|
|
|
// state information.
|
|
|
|
type monitor struct {
|
|
|
|
ui cli.Ui
|
|
|
|
client *api.Client
|
2015-09-16 23:27:55 +00:00
|
|
|
state *evalState
|
2016-01-19 23:02:17 +00:00
|
|
|
|
|
|
|
// length determines the number of characters for identifiers in the ui.
|
2016-01-14 20:57:43 +00:00
|
|
|
length int
|
2015-09-16 20:58:33 +00:00
|
|
|
|
|
|
|
sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// newMonitor returns a new monitor. The returned monitor will
|
2016-01-19 23:02:17 +00:00
|
|
|
// write output information to the provided ui. The length parameter determines
|
|
|
|
// the number of characters for identifiers in the ui.
|
2016-01-14 20:57:43 +00:00
|
|
|
func newMonitor(ui cli.Ui, client *api.Client, length int) *monitor {
|
2018-03-30 22:44:06 +00:00
|
|
|
if colorUi, ok := ui.(*cli.ColoredUi); ok {
|
|
|
|
// Disable Info color for monitored output
|
|
|
|
ui = &cli.ColoredUi{
|
|
|
|
ErrorColor: colorUi.ErrorColor,
|
|
|
|
WarnColor: colorUi.WarnColor,
|
|
|
|
InfoColor: cli.UiColorNone,
|
|
|
|
Ui: colorUi.Ui,
|
|
|
|
}
|
|
|
|
}
|
2015-09-18 17:03:23 +00:00
|
|
|
mon := &monitor{
|
2015-09-17 00:36:14 +00:00
|
|
|
ui: &cli.PrefixedUi{
|
|
|
|
InfoPrefix: "==> ",
|
|
|
|
OutputPrefix: " ",
|
|
|
|
ErrorPrefix: "==> ",
|
|
|
|
Ui: ui,
|
|
|
|
},
|
2015-09-16 20:58:33 +00:00
|
|
|
client: client,
|
2015-09-22 17:27:30 +00:00
|
|
|
state: newEvalState(),
|
2016-01-14 20:57:43 +00:00
|
|
|
length: length,
|
2015-09-16 20:58:33 +00:00
|
|
|
}
|
2015-09-18 17:03:23 +00:00
|
|
|
return mon
|
2015-09-16 20:58:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// update is used to update our monitor with new state. It can be
|
|
|
|
// called whether the passed information is new or not, and will
|
|
|
|
// only dump update messages when state changes.
|
2015-09-21 18:25:22 +00:00
|
|
|
func (m *monitor) update(update *evalState) {
|
2015-09-16 20:58:33 +00:00
|
|
|
m.Lock()
|
|
|
|
defer m.Unlock()
|
|
|
|
|
|
|
|
existing := m.state
|
|
|
|
|
2015-09-21 18:25:22 +00:00
|
|
|
// Swap in the new state at the end
|
|
|
|
defer func() {
|
|
|
|
m.state = update
|
|
|
|
}()
|
2015-09-16 20:58:33 +00:00
|
|
|
|
2015-09-21 22:55:10 +00:00
|
|
|
// Check if the evaluation was triggered by a node
|
|
|
|
if existing.node == "" && update.node != "" {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Output(fmt.Sprintf("%s: Evaluation triggered by node %q",
|
|
|
|
formatTime(time.Now()), limit(update.node, m.length)))
|
2015-09-21 22:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the evaluation was triggered by a job
|
|
|
|
if existing.job == "" && update.job != "" {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Output(fmt.Sprintf("%s: Evaluation triggered by job %q",
|
|
|
|
formatTime(time.Now()), update.job))
|
2015-09-21 22:55:10 +00:00
|
|
|
}
|
|
|
|
|
2017-07-07 02:55:58 +00:00
|
|
|
// Check if the evaluation was triggered by a deployment
|
|
|
|
if existing.deployment == "" && update.deployment != "" {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Output(fmt.Sprintf("%s: Evaluation within deployment: %q",
|
|
|
|
formatTime(time.Now()), limit(update.deployment, m.length)))
|
2017-07-07 02:55:58 +00:00
|
|
|
}
|
|
|
|
|
2015-09-16 21:45:21 +00:00
|
|
|
// Check the allocations
|
|
|
|
for allocID, alloc := range update.allocs {
|
2015-09-16 22:37:08 +00:00
|
|
|
if existing, ok := existing.allocs[allocID]; !ok {
|
2015-09-18 03:18:33 +00:00
|
|
|
switch {
|
|
|
|
case alloc.index < update.index:
|
|
|
|
// New alloc with create index lower than the eval
|
|
|
|
// create index indicates modification
|
2015-09-18 17:03:23 +00:00
|
|
|
m.ui.Output(fmt.Sprintf(
|
2021-05-20 23:19:39 +00:00
|
|
|
"%s: Allocation %q modified: node %q, group %q",
|
|
|
|
formatTime(time.Now()), limit(alloc.id, m.length),
|
|
|
|
limit(alloc.node, m.length), alloc.group))
|
2015-09-18 03:18:33 +00:00
|
|
|
|
2022-08-02 14:33:08 +00:00
|
|
|
case alloc.desired == api.AllocDesiredStatusRun:
|
2015-09-18 03:18:33 +00:00
|
|
|
// New allocation with desired status running
|
2015-09-18 17:03:23 +00:00
|
|
|
m.ui.Output(fmt.Sprintf(
|
2021-05-20 23:19:39 +00:00
|
|
|
"%s: Allocation %q created: node %q, group %q",
|
|
|
|
formatTime(time.Now()), limit(alloc.id, m.length),
|
|
|
|
limit(alloc.node, m.length), alloc.group))
|
2015-09-16 22:37:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
2015-09-18 03:18:33 +00:00
|
|
|
switch {
|
|
|
|
case existing.client != alloc.client:
|
2016-05-25 01:42:05 +00:00
|
|
|
description := ""
|
|
|
|
if alloc.clientDesc != "" {
|
|
|
|
description = fmt.Sprintf(" (%s)", alloc.clientDesc)
|
|
|
|
}
|
2015-09-18 03:18:33 +00:00
|
|
|
// Allocation status has changed
|
2015-09-18 17:03:23 +00:00
|
|
|
m.ui.Output(fmt.Sprintf(
|
2021-05-20 23:19:39 +00:00
|
|
|
"%s: Allocation %q status changed: %q -> %q%s",
|
|
|
|
formatTime(time.Now()), limit(alloc.id, m.length),
|
|
|
|
existing.client, alloc.client, description))
|
2015-09-16 21:45:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-21 19:53:48 +00:00
|
|
|
// Check if the status changed. We skip any transitions to pending status.
|
|
|
|
if existing.status != "" &&
|
2022-08-02 14:33:08 +00:00
|
|
|
update.status != api.AllocClientStatusPending &&
|
2015-09-21 19:53:48 +00:00
|
|
|
existing.status != update.status {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Output(fmt.Sprintf("%s: Evaluation status changed: %q -> %q",
|
|
|
|
formatTime(time.Now()), existing.status, update.status))
|
2015-09-16 20:58:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// monitor is used to start monitoring the given evaluation ID. It
|
|
|
|
// writes output directly to the monitor's ui, and returns the
|
2020-11-02 18:24:49 +00:00
|
|
|
// exit code for the command.
|
2015-09-21 19:19:34 +00:00
|
|
|
//
|
|
|
|
// The return code will be 0 on successful evaluation. If there are
|
|
|
|
// problems scheduling the job (impossible constraints, resources
|
|
|
|
// exhausted, etc), then the return code will be 2. For any other
|
|
|
|
// failures (API connectivity, internal errors, etc), the return code
|
|
|
|
// will be 1.
|
2020-11-02 18:24:49 +00:00
|
|
|
func (m *monitor) monitor(evalID string) int {
|
2015-09-21 19:19:34 +00:00
|
|
|
// Track if we encounter a scheduling failure. This can only be
|
|
|
|
// detected while querying allocations, so we use this bool to
|
|
|
|
// carry that status into the return code.
|
|
|
|
var schedFailure bool
|
|
|
|
|
2015-09-22 17:27:30 +00:00
|
|
|
// Add the initial pending state
|
|
|
|
m.update(newEvalState())
|
|
|
|
|
2022-07-06 14:13:48 +00:00
|
|
|
m.ui.Info(fmt.Sprintf("%s: Monitoring evaluation %q",
|
|
|
|
formatTime(time.Now()), limit(evalID, m.length)))
|
|
|
|
|
2015-09-16 20:58:33 +00:00
|
|
|
for {
|
2015-09-16 23:27:55 +00:00
|
|
|
// Query the evaluation
|
2015-09-16 20:58:33 +00:00
|
|
|
eval, _, err := m.client.Evaluations().Info(evalID, nil)
|
|
|
|
if err != nil {
|
2020-11-02 18:24:49 +00:00
|
|
|
m.ui.Error(fmt.Sprintf("No evaluation with id %q found", evalID))
|
|
|
|
return 1
|
2015-09-16 20:58:33 +00:00
|
|
|
}
|
|
|
|
|
2015-09-21 18:25:22 +00:00
|
|
|
// Create the new eval state.
|
2015-09-22 17:27:30 +00:00
|
|
|
state := newEvalState()
|
|
|
|
state.status = eval.Status
|
|
|
|
state.desc = eval.StatusDescription
|
|
|
|
state.node = eval.NodeID
|
|
|
|
state.job = eval.JobID
|
2017-07-07 02:55:58 +00:00
|
|
|
state.deployment = eval.DeploymentID
|
2015-09-22 17:27:30 +00:00
|
|
|
state.wait = eval.Wait
|
|
|
|
state.index = eval.CreateIndex
|
2015-09-21 18:25:22 +00:00
|
|
|
|
2015-09-16 23:27:55 +00:00
|
|
|
// Query the allocations associated with the evaluation
|
2015-12-19 20:05:17 +00:00
|
|
|
allocs, _, err := m.client.Evaluations().Allocations(eval.ID, nil)
|
2015-09-16 21:45:21 +00:00
|
|
|
if err != nil {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Error(fmt.Sprintf("%s: Error reading allocations: %s", formatTime(time.Now()), err))
|
2015-09-16 21:45:21 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2015-09-21 18:25:22 +00:00
|
|
|
// Add the allocs to the state
|
|
|
|
for _, alloc := range allocs {
|
|
|
|
state.allocs[alloc.ID] = &allocState{
|
|
|
|
id: alloc.ID,
|
|
|
|
group: alloc.TaskGroup,
|
|
|
|
node: alloc.NodeID,
|
|
|
|
desired: alloc.DesiredStatus,
|
|
|
|
desiredDesc: alloc.DesiredDescription,
|
|
|
|
client: alloc.ClientStatus,
|
2015-09-21 22:55:10 +00:00
|
|
|
clientDesc: alloc.ClientDescription,
|
2015-09-21 18:25:22 +00:00
|
|
|
index: alloc.CreateIndex,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-16 20:58:33 +00:00
|
|
|
// Update the state
|
2015-09-21 18:25:22 +00:00
|
|
|
m.update(state)
|
2015-09-16 21:45:21 +00:00
|
|
|
|
|
|
|
switch eval.Status {
|
2022-08-02 14:33:08 +00:00
|
|
|
case api.EvalStatusComplete, api.EvalStatusFailed, api.EvalStatusCancelled:
|
2016-05-19 05:02:51 +00:00
|
|
|
if len(eval.FailedTGAllocs) == 0 {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Info(fmt.Sprintf("%s: Evaluation %q finished with status %q",
|
|
|
|
formatTime(time.Now()), limit(eval.ID, m.length), eval.Status))
|
2016-05-19 05:02:51 +00:00
|
|
|
} else {
|
|
|
|
// There were failures making the allocations
|
2016-05-25 01:42:05 +00:00
|
|
|
schedFailure = true
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Info(fmt.Sprintf("%s: Evaluation %q finished with status %q but failed to place all allocations:",
|
|
|
|
formatTime(time.Now()), limit(eval.ID, m.length), eval.Status))
|
2016-05-19 05:02:51 +00:00
|
|
|
|
|
|
|
// Print the failures per task group
|
|
|
|
for tg, metrics := range eval.FailedTGAllocs {
|
|
|
|
noun := "allocation"
|
|
|
|
if metrics.CoalescedFailures > 0 {
|
|
|
|
noun += "s"
|
|
|
|
}
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Output(fmt.Sprintf("%s: Task Group %q (failed to place %d %s):",
|
|
|
|
formatTime(time.Now()), tg, metrics.CoalescedFailures+1, noun))
|
2016-05-31 21:51:23 +00:00
|
|
|
metrics := formatAllocMetrics(metrics, false, " ")
|
|
|
|
for _, line := range strings.Split(metrics, "\n") {
|
|
|
|
m.ui.Output(line)
|
|
|
|
}
|
2016-05-19 05:02:51 +00:00
|
|
|
}
|
2016-05-19 20:16:10 +00:00
|
|
|
|
2016-05-25 01:12:59 +00:00
|
|
|
if eval.BlockedEval != "" {
|
2021-05-20 23:19:39 +00:00
|
|
|
m.ui.Output(fmt.Sprintf("%s: Evaluation %q waiting for additional capacity to place remainder",
|
|
|
|
formatTime(time.Now()), limit(eval.BlockedEval, m.length)))
|
2016-05-19 20:16:10 +00:00
|
|
|
}
|
2016-05-19 05:02:51 +00:00
|
|
|
}
|
2015-09-16 21:45:21 +00:00
|
|
|
default:
|
|
|
|
// Wait for the next update
|
2015-09-18 17:03:23 +00:00
|
|
|
time.Sleep(updateWait)
|
2015-09-16 21:45:21 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-09-21 22:55:10 +00:00
|
|
|
// Monitor the next eval in the chain, if present
|
2015-09-16 21:45:21 +00:00
|
|
|
if eval.NextEval != "" {
|
2016-02-07 00:09:39 +00:00
|
|
|
if eval.Wait.Nanoseconds() != 0 {
|
|
|
|
m.ui.Info(fmt.Sprintf(
|
2021-05-20 23:19:39 +00:00
|
|
|
"%s: Monitoring next evaluation %q in %s",
|
|
|
|
formatTime(time.Now()), limit(eval.NextEval, m.length), eval.Wait))
|
2015-09-21 22:55:10 +00:00
|
|
|
|
2016-02-07 00:09:39 +00:00
|
|
|
// Skip some unnecessary polling
|
|
|
|
time.Sleep(eval.Wait)
|
|
|
|
}
|
2015-09-21 22:55:10 +00:00
|
|
|
|
2015-09-22 17:27:30 +00:00
|
|
|
// Reset the state and monitor the new eval
|
|
|
|
m.state = newEvalState()
|
2020-11-02 18:24:49 +00:00
|
|
|
return m.monitor(eval.NextEval)
|
2015-09-16 21:45:21 +00:00
|
|
|
}
|
2015-09-16 23:20:19 +00:00
|
|
|
break
|
2015-09-16 20:58:33 +00:00
|
|
|
}
|
|
|
|
|
2021-06-14 23:42:38 +00:00
|
|
|
// Monitor the deployment if it exists
|
2021-05-20 23:19:39 +00:00
|
|
|
dID := m.state.deployment
|
2021-06-14 23:42:38 +00:00
|
|
|
if dID != "" {
|
|
|
|
m.ui.Info(fmt.Sprintf("%s: Monitoring deployment %q", formatTime(time.Now()), limit(dID, m.length)))
|
2021-05-20 23:19:39 +00:00
|
|
|
|
2021-06-14 23:42:38 +00:00
|
|
|
var verbose bool
|
|
|
|
if m.length == fullId {
|
|
|
|
verbose = true
|
|
|
|
} else {
|
|
|
|
verbose = false
|
|
|
|
}
|
2021-05-20 23:19:39 +00:00
|
|
|
|
2021-06-14 23:42:38 +00:00
|
|
|
meta := new(Meta)
|
|
|
|
meta.Ui = m.ui
|
|
|
|
cmd := &DeploymentStatusCommand{Meta: *meta}
|
2022-11-23 21:36:13 +00:00
|
|
|
status, err := cmd.monitor(m.client, dID, 0, m.state.wait, verbose)
|
2022-08-02 14:33:08 +00:00
|
|
|
if err != nil || status != api.DeploymentStatusSuccessful {
|
2021-12-09 14:09:28 +00:00
|
|
|
return 1
|
|
|
|
}
|
2021-06-14 23:42:38 +00:00
|
|
|
}
|
2021-05-20 23:19:39 +00:00
|
|
|
|
2015-09-21 19:19:34 +00:00
|
|
|
// Treat scheduling failures specially using a dedicated exit code.
|
|
|
|
// This makes it easier to detect failures from the CLI.
|
|
|
|
if schedFailure {
|
|
|
|
return 2
|
|
|
|
}
|
|
|
|
|
2015-09-16 20:58:33 +00:00
|
|
|
return 0
|
|
|
|
}
|
2015-09-21 19:31:06 +00:00
|
|
|
|
2016-05-31 21:51:23 +00:00
|
|
|
func formatAllocMetrics(metrics *api.AllocationMetric, scores bool, prefix string) string {
|
2015-09-21 23:20:50 +00:00
|
|
|
// Print a helpful message if we have an eligibility problem
|
2016-05-31 21:51:23 +00:00
|
|
|
var out string
|
2016-05-19 05:02:51 +00:00
|
|
|
if metrics.NodesEvaluated == 0 {
|
2016-05-31 21:51:23 +00:00
|
|
|
out += fmt.Sprintf("%s* No nodes were eligible for evaluation\n", prefix)
|
2015-09-21 23:20:50 +00:00
|
|
|
}
|
|
|
|
|
2016-01-04 22:23:06 +00:00
|
|
|
// Print a helpful message if the user has asked for a DC that has no
|
|
|
|
// available nodes.
|
2016-05-19 05:02:51 +00:00
|
|
|
for dc, available := range metrics.NodesAvailable {
|
2016-01-04 22:23:06 +00:00
|
|
|
if available == 0 {
|
2016-05-31 21:51:23 +00:00
|
|
|
out += fmt.Sprintf("%s* No nodes are available in datacenter %q\n", prefix, dc)
|
2016-01-04 22:23:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-21 22:55:10 +00:00
|
|
|
// Print filter info
|
2016-05-19 05:02:51 +00:00
|
|
|
for class, num := range metrics.ClassFiltered {
|
2020-03-17 18:45:11 +00:00
|
|
|
out += fmt.Sprintf("%s* Class %q: %d nodes excluded by filter\n", prefix, class, num)
|
2015-09-21 22:55:10 +00:00
|
|
|
}
|
2016-05-19 05:02:51 +00:00
|
|
|
for cs, num := range metrics.ConstraintFiltered {
|
2020-03-17 18:45:11 +00:00
|
|
|
out += fmt.Sprintf("%s* Constraint %q: %d nodes excluded by filter\n", prefix, cs, num)
|
2015-09-21 22:55:10 +00:00
|
|
|
}
|
|
|
|
|
2015-09-21 19:31:06 +00:00
|
|
|
// Print exhaustion info
|
2016-05-19 05:02:51 +00:00
|
|
|
if ne := metrics.NodesExhausted; ne > 0 {
|
2016-05-31 21:51:23 +00:00
|
|
|
out += fmt.Sprintf("%s* Resources exhausted on %d nodes\n", prefix, ne)
|
2015-09-21 19:31:06 +00:00
|
|
|
}
|
2016-05-19 05:02:51 +00:00
|
|
|
for class, num := range metrics.ClassExhausted {
|
2016-05-31 21:51:23 +00:00
|
|
|
out += fmt.Sprintf("%s* Class %q exhausted on %d nodes\n", prefix, class, num)
|
2015-09-21 19:31:06 +00:00
|
|
|
}
|
2016-05-19 05:02:51 +00:00
|
|
|
for dim, num := range metrics.DimensionExhausted {
|
2016-05-31 21:51:23 +00:00
|
|
|
out += fmt.Sprintf("%s* Dimension %q exhausted on %d nodes\n", prefix, dim, num)
|
2015-09-21 19:31:06 +00:00
|
|
|
}
|
2015-09-27 20:59:27 +00:00
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
// Print quota info
|
|
|
|
for _, dim := range metrics.QuotaExhausted {
|
|
|
|
out += fmt.Sprintf("%s* Quota limit hit %q\n", prefix, dim)
|
|
|
|
}
|
|
|
|
|
2015-09-27 20:59:27 +00:00
|
|
|
// Print scores
|
2016-05-19 05:02:51 +00:00
|
|
|
if scores {
|
2018-08-08 14:41:56 +00:00
|
|
|
if len(metrics.ScoreMetaData) > 0 {
|
2018-08-17 23:44:00 +00:00
|
|
|
scoreOutput := make([]string, len(metrics.ScoreMetaData)+1)
|
2019-11-01 17:58:22 +00:00
|
|
|
|
2021-09-08 21:30:11 +00:00
|
|
|
// Find all possible scores and build header row.
|
|
|
|
allScores := make(map[string]struct{})
|
|
|
|
for _, scoreMeta := range metrics.ScoreMetaData {
|
|
|
|
for score := range scoreMeta.Scores {
|
|
|
|
allScores[score] = struct{}{}
|
2018-08-17 23:44:00 +00:00
|
|
|
}
|
2021-09-08 21:30:11 +00:00
|
|
|
}
|
|
|
|
// Sort scores alphabetically.
|
|
|
|
scores := make([]string, 0, len(allScores))
|
|
|
|
for score := range allScores {
|
|
|
|
scores = append(scores, score)
|
|
|
|
}
|
|
|
|
sort.Strings(scores)
|
|
|
|
scoreOutput[0] = fmt.Sprintf("Node|%s|final score", strings.Join(scores, "|"))
|
|
|
|
|
|
|
|
// Build row for each score.
|
|
|
|
for i, scoreMeta := range metrics.ScoreMetaData {
|
2018-08-17 23:44:00 +00:00
|
|
|
scoreOutput[i+1] = fmt.Sprintf("%v|", scoreMeta.NodeID)
|
2021-09-08 21:30:11 +00:00
|
|
|
for _, scorerName := range scores {
|
2018-10-18 02:06:24 +00:00
|
|
|
scoreVal := scoreMeta.Scores[scorerName]
|
|
|
|
scoreOutput[i+1] += fmt.Sprintf("%.3g|", scoreVal)
|
2018-08-08 14:41:56 +00:00
|
|
|
}
|
2018-10-18 02:06:24 +00:00
|
|
|
scoreOutput[i+1] += fmt.Sprintf("%.3g", scoreMeta.NormScore)
|
2018-08-08 14:41:56 +00:00
|
|
|
}
|
2021-09-08 21:30:11 +00:00
|
|
|
|
2018-08-17 23:44:00 +00:00
|
|
|
out += formatList(scoreOutput)
|
2018-08-08 14:41:56 +00:00
|
|
|
} else {
|
|
|
|
// Backwards compatibility for old allocs
|
|
|
|
for name, score := range metrics.Scores {
|
|
|
|
out += fmt.Sprintf("%s* Score %q = %f\n", prefix, name, score)
|
|
|
|
}
|
2016-05-19 05:02:51 +00:00
|
|
|
}
|
2015-09-27 20:59:27 +00:00
|
|
|
}
|
2016-05-31 21:51:23 +00:00
|
|
|
|
|
|
|
out = strings.TrimSuffix(out, "\n")
|
|
|
|
return out
|
2015-09-21 19:31:06 +00:00
|
|
|
}
|