diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 0ca562e..71b780b 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -430,6 +430,11 @@ func processCommonFlags(cmd *cobra.Command) (commonFlags, error) { f.outputAs = "json" case "yaml": f.outputAs = "yaml" + case "colour": + fallthrough + case "color": + f.outputAs = "colour" + default: return commonFlags{}, errors.New("unknown output format only csv, list, json and yaml are supported") } diff --git a/pkg/plugin/resources.go b/pkg/plugin/resources.go index e8241d3..e58eb55 100644 --- a/pkg/plugin/resources.go +++ b/pkg/plugin/resources.go @@ -205,6 +205,7 @@ func (s *resource) BuildBranch(info BuilderInformation, rows [][]Cell) ([]Cell, val := validateFloat64(float64(rowOut[0].number) / float64(rowOut[1].number) * 100) rowOut[4].text = fmt.Sprintf(floatfmt, val) rowOut[4].float = val + rowOut[4].colour = setColourValue(int(val), 90, 75) } if rowOut[2].number > 0.0 { @@ -212,7 +213,17 @@ func (s *resource) BuildBranch(info BuilderInformation, rows [][]Cell) ([]Cell, val := validateFloat64(float64(rowOut[0].number) / float64(rowOut[2].number) * 100) rowOut[3].text = fmt.Sprintf(floatfmt, val) rowOut[3].float = val + rowOut[3].colour = setColourValue(int(val), 90, 75) } + + usedColour := 0 + if rowOut[3].float > rowOut[4].float { + usedColour = setColourValue(int(rowOut[4].float), 90, 75) + } else { + usedColour = setColourValue(int(rowOut[3].float), 90, 75) + } + + rowOut[0].colour = usedColour } return rowOut, nil @@ -238,6 +249,7 @@ func (s *resource) statsProcessTableRow(res v1.ResourceRequirements, metrics v1. var rawRequest, rawLimit, rawValue int64 var rawPercentRequest, rawPercentLimit float64 var requestCell, limitCell Cell + var percentRequestColour, percentLimitColour int log := logger{location: "resources:statsProcessTableRow"} log.Debug("Start") @@ -288,6 +300,8 @@ func (s *resource) statsProcessTableRow(res v1.ResourceRequirements, metrics v1. val := validateFloat64(cpuVal / res.Limits.Cpu().AsApproximateFloat64() * 100) percentLimit = fmt.Sprintf(floatfmt, val) rawPercentLimit = val + + percentLimitColour = setColourValue(int(val), 90, 75) } // check cpu requests has a value if res.Requests.Cpu().AsApproximateFloat64() == 0 { @@ -297,6 +311,8 @@ func (s *resource) statsProcessTableRow(res v1.ResourceRequirements, metrics v1. val := validateFloat64(cpuVal / res.Requests.Cpu().AsApproximateFloat64() * 100) percentRequest = fmt.Sprintf(floatfmt, val) rawPercentRequest = val + + percentRequestColour = setColourValue(int(val), 90, 75) } } } @@ -331,30 +347,43 @@ func (s *resource) statsProcessTableRow(res v1.ResourceRequirements, metrics v1. if res.Limits.Memory().AsApproximateFloat64() == 0 { percentLimit = "-" rawPercentLimit = 0.0 + percentLimitColour = -1 } else { val := validateFloat64(memVal / res.Limits.Memory().AsApproximateFloat64() * 100) percentLimit = fmt.Sprintf(floatfmt, val) rawPercentLimit = val + + percentLimitColour = setColourValue(int(val), 90, 75) } // check memory requests has a value if res.Requests.Memory().AsApproximateFloat64() == 0 { percentRequest = "-" rawPercentRequest = 0.0 + percentRequestColour = -1 } else { val := validateFloat64(memVal / res.Requests.Memory().AsApproximateFloat64() * 100) percentRequest = fmt.Sprintf(floatfmt, val) rawPercentRequest = val + + percentRequestColour = setColourValue(int(val), 90, 75) } } } } + usedColour := 0 + if percentLimitColour > percentRequestColour { + usedColour = percentLimitColour + } else { + usedColour = percentRequestColour + } + cellList = append(cellList, - NewCellInt(displayValue, rawValue), + NewCellColourInt(usedColour, displayValue, rawValue), requestCell, limitCell, - NewCellFloat(percentRequest, rawPercentRequest), - NewCellFloat(percentLimit, rawPercentLimit), + NewCellColourFloat(percentRequestColour, percentRequest, rawPercentRequest), + NewCellColourFloat(percentLimitColour, percentLimit, rawPercentLimit), ) log.Debug("cellList", cellList) diff --git a/pkg/plugin/status.go b/pkg/plugin/status.go index 2e4b97f..a11f14c 100644 --- a/pkg/plugin/status.go +++ b/pkg/plugin/status.go @@ -201,16 +201,20 @@ func (s *status) BuildBranch(info BuilderInformation, rows [][]Cell) ([]Cell, er // rowOut[10] // message rowOut[0].text = "true" + rowOut[0].colour = colourOk rowOut[1].text = "true" + rowOut[1].colour = colourOk // loop through each row in podTotals and add the columns in each row for _, r := range rows { if r[0].text == "false" { // ready = false rowOut[0].text = "false" // ready + rowOut[0].colour = colourBad } if r[1].text == "false" { rowOut[1].text = "false" // started + rowOut[1].colour = colourBad } rowOut[2].number += r[2].number // restarts @@ -226,6 +230,7 @@ func (s *status) BuildBranch(info BuilderInformation, rows [][]Cell) ([]Cell, er rowOut[3].text = string(info.Data.pod.Status.Phase) // state } else { rowOut[3].text = "Terminating" // state + rowOut[3].colour = colourWarn } rowOut[4].text = info.Data.pod.Status.Reason // reason rowOut[8].text = info.Data.pod.CreationTimestamp.Format(timestampFormat) // timestamp @@ -250,6 +255,7 @@ func (s *status) BuildContainerStatus(container v1.ContainerStatus, info Builder var age string var state v1.ContainerState var rawExitCode, rawSignal, rawRestarts int64 + var colourcode, readyColour, startColour int // var id string log := logger{location: "Status:BuildContainerStatus"} @@ -267,6 +273,7 @@ func (s *status) BuildContainerStatus(container v1.ContainerStatus, info Builder message = state.Waiting.Message // waiting state dosent have a start time so we skip setting the age variable, used further down skipAgeCalculation = true + colourcode = colourWarn } if state.Terminated != nil { @@ -279,12 +286,19 @@ func (s *status) BuildContainerStatus(container v1.ContainerStatus, info Builder startedAt = state.Terminated.StartedAt.Format(timestampFormat) reason = state.Terminated.Reason message = state.Terminated.Message + + if rawExitCode == 0 { + colourcode = colourOk + } else { + colourcode = colourBad + } } if state.Running != nil { strState = "Running" startedAt = state.Running.StartedAt.Format(timestampFormat) startTime = state.Running.StartedAt.Time + colourcode = colourOk } if container.Started != nil { @@ -292,12 +306,17 @@ func (s *status) BuildContainerStatus(container v1.ContainerStatus, info Builder if !*container.Started { s.pStopped = true } + // set started colour true = green and false = red + startColour = setColourBoolean(*container.Started) } ready := fmt.Sprintf("%t", container.Ready) if !container.Ready { s.pNotReady = true } + // set ready colour + readyColour = setColourBoolean(container.Ready) + restarts := fmt.Sprintf("%d", container.RestartCount) rawRestarts = int64(container.RestartCount) @@ -319,12 +338,12 @@ func (s *status) BuildContainerStatus(container v1.ContainerStatus, info Builder // READY STARTED RESTARTS STATE REASON EXIT-CODE SIGNAL TIMESTAMP AGE MESSAGE cellList = append(cellList, - NewCellText(ready), - NewCellText(started), + NewCellColourText(readyColour, ready), + NewCellColourText(startColour, started), NewCellInt(restarts, rawRestarts), - NewCellText(strState), + NewCellColourText(colourcode, strState), NewCellText(reason), - NewCellInt(exitCode, rawExitCode), + NewCellColourInt(colourcode, exitCode, rawExitCode), NewCellInt(signal, rawSignal), NewCellText(container.ContainerID), NewCellText(startedAt), diff --git a/pkg/plugin/table.go b/pkg/plugin/table.go index 510b92d..a95f641 100644 --- a/pkg/plugin/table.go +++ b/pkg/plugin/table.go @@ -24,6 +24,7 @@ type Cell struct { typ int // 0=string, 1=int64, 2=float64, 3=placeholder phRef int // placeholder reference id, used to track the row thats used as a placeholder indent int // the number of indents required in the output + colour int } type Table struct { @@ -171,7 +172,7 @@ func (t *Table) HideOnlyNamedColumns(columnName []string) error { } // Print outputs the table on the terminal, taking the column order and visibiliy into account -func (t *Table) Print() { +func (t *Table) Print(withColour bool) { headLine := "" // loop through all headers and make a single line properly spaced for col := 0; col < t.headCount; col++ { @@ -210,6 +211,7 @@ func (t *Table) Print() { } // now loop through each column in the currentl selected row for col := 0; col < t.headCount; col++ { + emptyCell := false idx := t.columnOrder[col] cell := row[idx] @@ -220,6 +222,7 @@ func (t *Table) Print() { if len(cell.text) == 0 { cell.text = "-" + emptyCell = true } celltxt := t.indentText(cell.indent, cell.text) @@ -228,6 +231,11 @@ func (t *Table) Print() { spaceCount = maxLineLength } pad := strings.Repeat(" ", spaceCount) + + if withColour && !emptyCell && cell.colour > -1 { + celltxt = fmt.Sprintf("\033[%dm%s%s", cell.colour, cell.text, colourEnd) + } + line += fmt.Sprint(celltxt, pad) } if !excludeRow { @@ -530,7 +538,8 @@ func strMatch(str string, pattern string) bool { func NewCellEmpty() Cell { return Cell{ - typ: -1, + typ: -1, + colour: -1, } } @@ -543,7 +552,8 @@ func NewCellText(text string) Cell { temp = strings.Replace(temp, "\t", "\\t", -1) return Cell{ - text: temp, + text: temp, + colour: -1, } } @@ -560,6 +570,7 @@ func NewCellTextIndent(text string, indentLevel int) Cell { return Cell{ text: temp, indent: indentLevel, + colour: -1, } } @@ -569,15 +580,51 @@ func NewCellInt(text string, value int64) Cell { text: text, number: value, typ: 1, + colour: -1, } } // NewCellFloat quick wrapper to return a cell object containing the given string float func NewCellFloat(text string, value float64) Cell { return Cell{ - text: text, - float: value, - typ: 2, + text: text, + float: value, + typ: 2, + colour: -1, + } +} + +// NewCellColourText quick wrapper to return a cell object containing the given string and the colour to be used +func NewCellColourText(colour int, text string) Cell { + + temp := strings.Replace(text, "\r", "\\r", -1) + temp = strings.Replace(temp, "\f", "\\f", -1) + temp = strings.Replace(temp, "\n", "\\n", -1) + temp = strings.Replace(temp, "\t", "\\t", -1) + + return Cell{ + text: temp, + colour: colour, + } +} + +// NewCellColorInt quick wrapper to return a cell object containing the given colour, string and int +func NewCellColourInt(colour int, text string, value int64) Cell { + return Cell{ + text: text, + number: value, + typ: 1, + colour: colour, + } +} + +// NewCellFloat quick wrapper to return a cell object containing the given colour, string and float +func NewCellColourFloat(colour int, text string, value float64) Cell { + return Cell{ + text: text, + float: value, + typ: 2, + colour: colour, } } diff --git a/pkg/plugin/table_test.go b/pkg/plugin/table_test.go index 581ed7e..58d7173 100644 --- a/pkg/plugin/table_test.go +++ b/pkg/plugin/table_test.go @@ -50,10 +50,10 @@ type addRowTest struct { } var addRowTests = []addRowTest{ - {[]Cell{NewCellText("one")}, 1, 5, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0}}}}, - {[]Cell{NewCellText("two")}, 2, 5, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0}}, {Cell{"two", 0, 0, 0, 0, 0}}}}, - {[]Cell{NewCellText("three")}, 3, 7, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0}}, {Cell{"two", 0, 0, 0, 0, 0}}, {Cell{"three", 0, 0, 0, 0, 0}}}}, - {[]Cell{NewCellText("four"), NewCellText("extra"), NewCellText("larger")}, 4, 7, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0}}, {Cell{"two", 0, 0, 0, 0, 0}}, {Cell{"three", 0, 0, 0, 0, 0}}, {Cell{"four", 0, 0, 0, 0, 0}, Cell{"extra", 0, 0, 0, 0, 0}, Cell{"larger", 0, 0, 0, 0, 0}}}}, + {[]Cell{NewCellText("one")}, 1, 5, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0, -1}}}}, + {[]Cell{NewCellText("two")}, 2, 5, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0, -1}}, {Cell{"two", 0, 0, 0, 0, 0, -1}}}}, + {[]Cell{NewCellText("three")}, 3, 7, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0, -1}}, {Cell{"two", 0, 0, 0, 0, 0, -1}}, {Cell{"three", 0, 0, 0, 0, 0, -1}}}}, + {[]Cell{NewCellText("four"), NewCellText("extra"), NewCellText("larger")}, 4, 7, [][]Cell{{Cell{"one", 0, 0, 0, 0, 0, -1}}, {Cell{"two", 0, 0, 0, 0, 0, -1}}, {Cell{"three", 0, 0, 0, 0, 0, -1}}, {Cell{"four", 0, 0, 0, 0, 0, -1}, Cell{"extra", 0, 0, 0, 0, 0, -1}, Cell{"larger", 0, 0, 0, 0, 0, -1}}}}, } func TestAddRow(t *testing.T) { diff --git a/pkg/plugin/utils.go b/pkg/plugin/utils.go index 0adfb30..5fe6eb9 100644 --- a/pkg/plugin/utils.go +++ b/pkg/plugin/utils.go @@ -8,6 +8,12 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) +const colourEnd = "\033[0m" +const colourNone = -1 +const colourBad = 31 +const colourOk = 32 +const colourWarn = 33 + // always returns false if the flagList.container is empty as we expect to show all containers // returns true if we dont have a match func skipContainerName(flagList commonFlags, containerName string) bool { @@ -91,7 +97,9 @@ func outputTableAs(t Table, outType string) { switch outType { case "": - t.Print() + t.Print(false) + case "colour": + t.Print(true) case "csv": t.PrintCsv() case "list": @@ -127,3 +135,28 @@ func portAsString(port intstr.IntOrString) string { return "" } + +func setColourValue(value int, bad int, warn int) int { + var colour int + + colour = colourOk + if value > bad { + colour = colourBad + } else if value > warn { + colour = colourWarn + } + + return colour +} + +func setColourBoolean(value bool) int { + var colour int + + if value { + colour = colourOk + } else { + colour = colourBad + } + + return colour +}