Skip to content

Commit 736cf3b

Browse files
committed
fix: add cluster and namespace to Top ui
plus some refactoring of Top into Header and JobBox
1 parent 10ced2a commit 736cf3b

File tree

6 files changed

+376
-157
lines changed

6 files changed

+376
-157
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2023 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type { Breakdown, HostRec, JobRec, PodRec } from "./types.js"
18+
19+
type Group = {
20+
groupIdx: number
21+
job: JobRec
22+
ctime: number
23+
hosts: HostRec[]
24+
pods: PodRec[]
25+
stats: { min: Breakdown; tot: Breakdown }
26+
}
27+
28+
export default Group
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2023 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from "react"
18+
import { Box, Spacer, Text } from "ink"
19+
20+
import type { Context } from "./types.js"
21+
22+
type Props = Context
23+
24+
export default class Header extends React.PureComponent<Props> {
25+
public render() {
26+
return (
27+
<Box flexDirection="column">
28+
<Box>
29+
<Text>
30+
<Text color="blue" bold>
31+
{"Cluster " /* Cheapo alignment with "Namespace" */}
32+
</Text>
33+
{this.props.cluster}
34+
</Text>
35+
36+
<Spacer />
37+
</Box>
38+
39+
<Box>
40+
<Box>
41+
<Text>
42+
<Text color="blue" bold>
43+
Namespace{" "}
44+
</Text>
45+
{this.props.namespace}
46+
</Text>
47+
</Box>
48+
</Box>
49+
</Box>
50+
)
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Copyright 2023 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from "react"
18+
import prettyMillis from "pretty-ms"
19+
import prettyBytes from "pretty-bytes"
20+
import { Box, Text, TextProps } from "ink"
21+
22+
import type Group from "./Group.js"
23+
import type { Breakdown, Resource } from "./types.js"
24+
25+
import { ValidResources } from "./types.js"
26+
27+
import { themes } from "../../controller/dashboard/job/utilization/theme.js"
28+
import { defaultUtilizationThemes } from "../../controller/dashboard/job/grids.js"
29+
30+
type Props = {
31+
group: Group
32+
isSelected: boolean | "no-selection"
33+
min: Breakdown
34+
}
35+
36+
/** Render one `group` (one job) */
37+
export default class JobBox extends React.PureComponent<Props> {
38+
/** Text to use for one cell's worth of time */
39+
private readonly block = "■" // "▇"
40+
41+
private get isSelected() {
42+
return this.props.isSelected === true
43+
}
44+
45+
private get hasSelection() {
46+
return this.props.isSelected !== "no-selection"
47+
}
48+
49+
private get name() {
50+
return this.props.group.job.name
51+
}
52+
53+
private get ctime() {
54+
return this.props.group.ctime
55+
}
56+
57+
private styleOfResource(resource: Resource): TextProps {
58+
switch (resource) {
59+
case "cpu":
60+
return themes[defaultUtilizationThemes["cpu%"]][2]
61+
62+
case "mem":
63+
return themes[defaultUtilizationThemes["mem%"]][2]
64+
65+
default:
66+
case "gpu":
67+
return themes[defaultUtilizationThemes["gpu%"]][2]
68+
}
69+
}
70+
71+
private styleOfGroup(baseStyle: TextProps): TextProps {
72+
return Object.assign({}, baseStyle, {
73+
dimColor: this.hasSelection && !this.isSelected,
74+
})
75+
}
76+
77+
private styleOfGroupForResource(resource: Resource): TextProps {
78+
return this.styleOfGroup(this.styleOfResource(resource))
79+
}
80+
81+
private format(quantity: number, resource: Resource) {
82+
switch (resource) {
83+
case "cpu": {
84+
const formattedQuantity =
85+
quantity < 1000 ? `${quantity}m` : (quantity / 1000).toFixed(2).replace(/0+$/, "").replace(/\.$/, "")
86+
return formattedQuantity + " " + (formattedQuantity === "1" ? "cpu" : "cpus")
87+
}
88+
case "mem":
89+
return prettyBytes(quantity, { space: false })
90+
default:
91+
case "gpu":
92+
return quantity + " gpus"
93+
}
94+
}
95+
96+
/** Render a number of cells in a line */
97+
private nCells(
98+
N: number,
99+
labelSingular: string,
100+
labelPlural: string,
101+
style: TextProps,
102+
value: number | string = N,
103+
key = labelSingular
104+
) {
105+
return (
106+
<Box key={key}>
107+
<Box marginRight={1}>
108+
{Array(N)
109+
.fill(0)
110+
.map((_, idx) => (
111+
<Text key={idx} {...style}>
112+
{this.block}
113+
</Text>
114+
))}
115+
</Box>
116+
<Box flexWrap="nowrap">
117+
<Text {...style}>
118+
{value} {value === 1 ? labelSingular : labelPlural}
119+
</Text>
120+
</Box>
121+
</Box>
122+
)
123+
}
124+
125+
/** Render cells for one `resource` for one `group` */
126+
private resourceLine(resource: Resource) {
127+
const unit = this.props.min[resource]
128+
const tot = this.props.group.stats.tot[resource]
129+
const amnt = tot / this.props.group.hosts.length
130+
const N = Math.round(amnt / unit)
131+
132+
return this.nCells(
133+
N,
134+
"",
135+
"",
136+
this.styleOfGroupForResource(resource),
137+
this.format(amnt, resource) + "/node",
138+
resource
139+
)
140+
}
141+
142+
private titleStyle(): TextProps {
143+
if (this.isSelected === true) {
144+
return {
145+
inverse: true,
146+
}
147+
} else if (this.hasSelection) {
148+
return { dimColor: true }
149+
} else {
150+
return { bold: true }
151+
}
152+
}
153+
154+
private title() {
155+
return <Text {...this.titleStyle()}>{this.name.slice(0, 34) + (this.name.length > 34 ? "…" : "")}</Text>
156+
}
157+
158+
private prettyPrintAge() {
159+
return prettyMillis(Date.now() - this.ctime, { unitCount: 2, secondsDecimalDigits: 0 }).padEnd(
160+
7 /* 'XXm YYs'.length */
161+
)
162+
}
163+
164+
private header() {
165+
return (
166+
<Box>
167+
<Box flexGrow={1}>{this.title()}</Box>
168+
<Box justifyContent="flex-end" marginLeft={2}>
169+
<Text color="cyan">{this.prettyPrintAge()}</Text>
170+
</Box>
171+
</Box>
172+
)
173+
}
174+
175+
private body() {
176+
const { hosts, pods, stats } = this.props.group
177+
178+
return (
179+
<React.Fragment>
180+
{this.nCells(hosts.length, "node", "nodes", this.styleOfGroup({ color: "yellow" }))}
181+
{ValidResources.filter((resource) => stats.tot[resource] !== 0).map((resource) => this.resourceLine(resource))}
182+
{this.nCells(pods.length / hosts.length, "worker/node", "workers/node", this.styleOfGroup({ color: "white" }))}
183+
</React.Fragment>
184+
)
185+
}
186+
187+
public render() {
188+
return (
189+
<Box flexDirection="column" borderStyle={this.isSelected ? "bold" : "single"}>
190+
{this.header()}
191+
{this.body()}
192+
</Box>
193+
)
194+
}
195+
}

0 commit comments

Comments
 (0)