Skip to content

Commit

Permalink
feat(plugins/plugin-kubectl): optimizing Node summary impl
Browse files Browse the repository at this point in the history
Fixes #4833
  • Loading branch information
starpit committed Jun 6, 2020
1 parent 1dd08fc commit e76bb4d
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 28 deletions.
1 change: 1 addition & 0 deletions plugins/plugin-kubectl/i18n/resources_en_US.json
@@ -1,4 +1,5 @@
{
"percentFree": "{0} Free",
"Apply Changes": "Apply Changes",
"Successfully Applied": "Successfully Applied",
"This API Resource is deprecated": "This API Resource is deprecated.",
Expand Down
3 changes: 3 additions & 0 deletions plugins/plugin-kubectl/src/index.ts
Expand Up @@ -103,3 +103,6 @@ export { viewTransformer as getTransformer } from './controller/kubectl/get'

/** A channel that covers *possible* changes to kubectl config */
export { onKubectlConfigChangeEvents, offKubectlConfigChangeEvents } from './controller/kubectl/config'

/** memory and cpu parsing */
export { default as Parser } from './lib/util/parse'
39 changes: 39 additions & 0 deletions plugins/plugin-kubectl/src/lib/model/resource.ts
Expand Up @@ -534,4 +534,43 @@ export function isClusterScoped(kind: string) {
return kind === 'CustomResourceDefinition' || kind === 'Namespace' || kind === 'Node'
}

interface NodeCapacity {
cpu: string
'ephemeral-storage': string
'hugepages-1Gi': string
'hugepages-2Mi': string
memory: string
pods: string
}

export type AddressType = 'InternalIP' | 'ExternalIP' | 'Hostname'
interface NodeStatus {
addresses: { address: string; type: AddressType }[]
allocatable: NodeCapacity
capacity: NodeCapacity
conditions: KubeStatusCondition[]
images: { names: string[]; sizeBytes: number }[]
nodeInfo: {
architecture: string
bootId: string
containerRuntimeVersion: string
kernelVersion: string
kubeProxyVersion: string
kubeletVersion: string
machineID: string
operatingSystem: string
osImage: string
systemUUID: string
}
}

export interface Node extends KubeResource<NodeStatus> {
apiVersion: 'v1'
kind: 'Node'
}

export function isNode(resource: KubeResource): resource is Node {
return resource.apiVersion === 'v1' && resource.kind === 'Node'
}

export default KubeResource
Expand Up @@ -55,6 +55,8 @@ export function memShare(str: string): number {
unit = megabytes
} else if (/Ki$/.test(str)) {
unit = kilobytes
} else if (/\d+/.test(str)) {
return parseInt(str)
} else {
end = 1
}
Expand All @@ -78,6 +80,16 @@ export function formatAsBytes(mem: number): string {
}
}

/** Turn e.g. 4041544Ki into 3947Mi */
export function reformatAsBytes(mem: string): string {
return formatAsBytes(memShare(mem))
}

/** Fraction of two string-form memory figures */
export function fractionOfMemory(num: string, denom: string): string {
return ((memShare(num) / memShare(denom)) * 100).toFixed(1) + '%'
}

export function formatAsCpu(cpu: number): string {
return cpu > 10000 ? (cpu / 1000).toFixed(0) : `${cpu}m`
}
Expand Down Expand Up @@ -128,4 +140,15 @@ export function parseAsSize(str: string): string {
return bytes(fromSize(str), {})
}

export default parseAsSize
/** Rollup export */
const Parser = {
fractionOfMemory,
reformatAsBytes,
formatAsBytes,
formatAsCpu,
cpuShare,
memShare,
cpuFraction
}

export default Parser
72 changes: 72 additions & 0 deletions plugins/plugin-kubectl/src/lib/view/modes/Summary/impl/Node.ts
@@ -0,0 +1,72 @@
/*
* Copyright 2020 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Name: 10.73.230.207
* Status: Ready
* Roles: <none>
* Age: 435d
* Version: v1.15.1+IKS
* Internal-ip: 10.73.230.207
* External-ip: 169.62.248.114
* Os-image: Ubuntu 16.04.6 LTS
* Kernel-version: 4.4.0-157-generic
* Container-runtime: 'containerd://1.2.7'
*
*/

import { i18n } from '@kui-shell/core'

import { age, none } from './Generic'
import Parser from '../../../../util/parse'
import { AddressType, Node } from '../../../../model/resource'

const strings = i18n('plugin-kubectl')

function address(type: AddressType, node: Node) {
const addr = node.status.addresses.find(_ => _.type === type)
return addr ? addr.address : none()
}

function tryParse(intAsString: string): number | string {
try {
return parseInt(intAsString)
} catch (err) {
return intAsString
}
}

function free(metric: 'memory' | 'ephemeral-storage', { status }: Node) {
return strings('percentFree', Parser.fractionOfMemory(status.allocatable[metric], status.capacity[metric]))
}

export default function NodeSummary(node: Node) {
const { metadata, status } = node

return {
Name: metadata.name,
Age: age(node),
CPUs: tryParse(status.capacity.cpu),
Memory: Parser.reformatAsBytes(status.capacity.memory) + ` (${free('memory', node)})`,
'Ephemeral Storage':
Parser.reformatAsBytes(status.capacity['ephemeral-storage']) + ` (${free('ephemeral-storage', node)})`,
'External IP': address('ExternalIP', node),
'Internal IP': address('InternalIP', node),
'OS Image': status.nodeInfo.osImage,
'Kernel Version': status.nodeInfo.kernelVersion,
'Container Runtime': status.nodeInfo.containerRuntimeVersion
}
}
8 changes: 6 additions & 2 deletions plugins/plugin-kubectl/src/lib/view/modes/Summary/index.ts
Expand Up @@ -21,13 +21,15 @@ import {
isSummarizableKubeResource,
isKubeResourceWithItsOwnSummary,
isDeployment,
isNode,
isPod,
isReplicaSet
} from '../../../model/resource'

import PodSummary from './impl/Pod'
import GenericSummary from './impl/Generic'
import DeploymentSummary from './impl/Deployment'
import GenericSummary from './impl/Generic'
import NodeSummary from './impl/Node'
import PodSummary from './impl/Pod'
import ReplicaSetSummary from './impl/ReplicaSet'

const strings = i18n('plugin-kubectl')
Expand All @@ -49,6 +51,8 @@ async function renderSummary({ REPL }: Tab, resource: KubeResource) {
? DeploymentSummary(resource)
: isReplicaSet(resource)
? ReplicaSetSummary(resource)
: isNode(resource)
? NodeSummary(resource)
: GenericSummary(resource, REPL)

// our content is that map, rendered as yaml
Expand Down
Expand Up @@ -15,11 +15,10 @@
*/

import { Arguments, Table, Row, RawResponse, i18n } from '@kui-shell/core'
import { KubeOptions } from '@kui-shell/plugin-kubectl'
import { KubeOptions, Parser } from '@kui-shell/plugin-kubectl'

import slash from '../view/slash'
import { BarColor, singletonBar as bar } from '../view/bar'
import { formatAsBytes, cpuFraction, cpuShare, memShare } from '../lib/parse'

const strings = i18n('plugin-kubectl', 'view-utilization-table')

Expand Down Expand Up @@ -106,17 +105,17 @@ export async function getSystemOverhead(
const detail = await getNodeData(args, onlySchedulable, forNode)

const cpuOverhead = detail.body.reduce((total, row) => {
return total + cpuShare(row.attributes[2].value) - cpuShare(row.attributes[0].value)
return total + Parser.cpuShare(row.attributes[2].value) - Parser.cpuShare(row.attributes[0].value)
}, 0)
const cpuCapacity = detail.body.reduce((total, row) => {
return total + cpuShare(row.attributes[2].value)
return total + Parser.cpuShare(row.attributes[2].value)
}, 0)

const memOverhead = detail.body.reduce((total, row) => {
return total + memShare(row.attributes[3].value) - memShare(row.attributes[1].value)
return total + Parser.memShare(row.attributes[3].value) - Parser.memShare(row.attributes[1].value)
}, 0)
const memCapacity = detail.body.reduce((total, row) => {
return total + memShare(row.attributes[3].value)
return total + Parser.memShare(row.attributes[3].value)
}, 0)

return { cpuOverhead, memOverhead, cpuCapacity, memCapacity }
Expand Down Expand Up @@ -167,9 +166,13 @@ async function summary(

// extract the summary statistics
const cpuFrac =
nodeTable.body.reduce((total, row) => total + cpuFraction(row.attributes[1].value), 0) / nodeTable.body.length / 100
nodeTable.body.reduce((total, row) => total + Parser.cpuFraction(row.attributes[1].value), 0) /
nodeTable.body.length /
100
const memFrac =
nodeTable.body.reduce((total, row) => total + cpuFraction(row.attributes[3].value), 0) / nodeTable.body.length / 100
nodeTable.body.reduce((total, row) => total + Parser.cpuFraction(row.attributes[3].value), 0) /
nodeTable.body.length /
100

// return as a RawResponse
return { mode: 'raw', content: { cpuFrac, memFrac } }
Expand Down Expand Up @@ -245,7 +248,7 @@ export async function topNode(
row.attributes[0].valueDom = slash(row.attributes[0].value, allocatableInfo.attributes[2].value)
row.attributes[2].valueDom = slash(
row.attributes[2].value,
formatAsBytes(memShare(allocatableInfo.attributes[3].value))
Parser.formatAsBytes(Parser.memShare(allocatableInfo.attributes[3].value))
)

// don't hide-with-sidecar the mem% column
Expand All @@ -259,7 +262,10 @@ export async function topNode(
row.attributes.push({
outerCSS: 'not-displayed',
key: 'Allocatable Memory',
value: allocatableInfo === undefined ? '&emdash;' : formatAsBytes(memShare(allocatableInfo.attributes[3].value))
value:
allocatableInfo === undefined
? '&emdash;'
: Parser.formatAsBytes(Parser.memShare(allocatableInfo.attributes[3].value))
})
}
})
Expand All @@ -269,12 +275,12 @@ export async function topNode(
const totalRow = JSON.parse(JSON.stringify(nodeTable.body[0]))
totalRow.name = strings('Total')
const cpuTotal = nodeTable.body.reduce((total, row) => total + cpuShare(row.attributes[0].value), 0)
const cpuFrac = nodeTable.body.reduce((total, row) => total + cpuFraction(row.attributes[1].value), 0)
const memTotal = nodeTable.body.reduce((total, row) => total + memShare(row.attributes[2].value), 0)
const memFrac = nodeTable.body.reduce((total, row) => total + cpuFraction(row.attributes[3].value), 0)
const cpuAllocTotal = nodeTable.body.reduce((total, row) => total + cpuShare(row.attributes[4].value), 0)
const memAllocTotal = nodeTable.body.reduce((total, row) => total + memShare(row.attributes[5].value), 0)
const cpuTotal = nodeTable.body.reduce((total, row) => total + Parser.cpuShare(row.attributes[0].value), 0)
const cpuFrac = nodeTable.body.reduce((total, row) => total + Parser.cpuFraction(row.attributes[1].value), 0)
const memTotal = nodeTable.body.reduce((total, row) => total + Parser.memShare(row.attributes[2].value), 0)
const memFrac = nodeTable.body.reduce((total, row) => total + Parser.cpuFraction(row.attributes[3].value), 0)
const cpuAllocTotal = nodeTable.body.reduce((total, row) => total + Parser.cpuShare(row.attributes[4].value), 0)
const memAllocTotal = nodeTable.body.reduce((total, row) => total + Parser.memShare(row.attributes[5].value), 0)
totalRow.onclick = false
totalRow.attributes[0].value = formatAsCpu(cpuTotal)
Expand Down
Expand Up @@ -16,6 +16,7 @@

import { Arguments, Table, i18n, isTable } from '@kui-shell/core'
import {
Parser,
KubeItems,
KubeOptions,
Pod,
Expand All @@ -25,7 +26,6 @@ import {
} from '@kui-shell/plugin-kubectl'

import { strip, getSystemOverhead } from './get-node-data'
import { formatAsBytes, formatAsCpu, cpuShare, memShare } from '../lib/parse'
// import { parseAsTime, parseAsSize } from '../lib/parse'

const strings = i18n('plugin-kubectl', 'view-utilization-table')
Expand Down Expand Up @@ -162,8 +162,8 @@ function addRow(
row.onclick = false
row.rowKey = key
row.name = strings(key)
row.attributes[0].value = formatAs === 'share' ? formatAsCpu(cpu) : (100 * cpu).toFixed(0) + '%'
row.attributes[1].value = formatAs === 'share' ? formatAsBytes(mem) : (100 * mem).toFixed(0) + '%'
row.attributes[0].value = formatAs === 'share' ? Parser.formatAsCpu(cpu) : (100 * cpu).toFixed(0) + '%'
row.attributes[1].value = formatAs === 'share' ? Parser.formatAsBytes(mem) : (100 * mem).toFixed(0) + '%'

if (css) {
row.css = css
Expand All @@ -176,16 +176,16 @@ function addRow(

async function addAllNSRow(args: Arguments<KubeOptions>, forThisNS: Table, forAllNS: Table) {
if (forThisNS.body && forThisNS.body.length > 0) {
const cpuTotal = forThisNS.body.reduce((total, row) => total + cpuShare(row.attributes[0].value), 0)
const memTotal = forThisNS.body.reduce((total, row) => total + memShare(row.attributes[1].value), 0)
const cpuTotal = forThisNS.body.reduce((total, row) => total + Parser.cpuShare(row.attributes[0].value), 0)
const memTotal = forThisNS.body.reduce((total, row) => total + Parser.memShare(row.attributes[1].value), 0)
addRow(forThisNS, 'Total', cpuTotal, memTotal)

if (forAllNS.body && forAllNS.body.length > 0) {
const thisNS = await getNamespace(args)
const otherNS = forAllNS.body.filter(_ => _.name !== thisNS)

const cpuTotal = otherNS.reduce((total, row) => total + cpuShare(row.attributes[1].value), 0)
const memTotal = otherNS.reduce((total, row) => total + memShare(row.attributes[2].value), 0)
const cpuTotal = otherNS.reduce((total, row) => total + Parser.cpuShare(row.attributes[1].value), 0)
const memTotal = otherNS.reduce((total, row) => total + Parser.memShare(row.attributes[2].value), 0)
addRow(forThisNS, 'Other Namespaces', cpuTotal, memTotal, lighterText)
}
}
Expand Down Expand Up @@ -285,9 +285,9 @@ export async function topPod(
const otherNSRow = table.body.find(_ => _.rowKey === 'Other Namespaces')
if (totalRow && otherNSRow) {
const totalCPU: number =
cpuShare(totalRow.attributes[0].value) + cpuShare(otherNSRow.attributes[0].value) + cpuOverhead
Parser.cpuShare(totalRow.attributes[0].value) + Parser.cpuShare(otherNSRow.attributes[0].value) + cpuOverhead
const totalMem: number =
memShare(totalRow.attributes[1].value) + memShare(otherNSRow.attributes[1].value) + memOverhead
Parser.memShare(totalRow.attributes[1].value) + Parser.memShare(otherNSRow.attributes[1].value) + memOverhead
// addRow(table, strings('Free Capacity'), 1 - totalCPU / cpuCapacity, 1 - totalMem / memCapacity, lighterText, 'percent')
addRow(table, strings('Overall Total'), totalCPU, totalMem, lighterText)
}
Expand Down

0 comments on commit e76bb4d

Please sign in to comment.