Skip to content

Commit e130619

Browse files
committed
feat: add gpu temperature to dashboard, and put charts in bottom left
1 parent 2067415 commit e130619

File tree

5 files changed

+163
-121
lines changed

5 files changed

+163
-121
lines changed

plugins/plugin-client-default/notebooks/dashboard.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout:
33
1:
4-
position: left
4+
position: default
55
maximized: true
66
2:
77
position: default
@@ -13,7 +13,6 @@ layout:
1313
---
1414

1515
--8<-- "./dashboard-summary.md"
16-
--8<-- "./dashboard-source.md"
1716
--8<-- "./dashboard-envvars.md"
1817
--8<-- "./dashboard-dependencies.md"
1918

@@ -25,6 +24,8 @@ layout:
2524
--8<-- "$LOGDIR/logs/job.txt"
2625
```
2726

27+
--8<-- "./dashboard-source.md"
28+
2829
=== "Advanced"
2930
=== "Node Utilization"
3031

@@ -51,10 +52,11 @@ layout:
5152

5253
---
5354

54-
```shell
55-
---
56-
execute: now
57-
outputOnly: true
58-
---
59-
chart "$LOGDIR/resources/gpu.txt"
60-
```
55+
=== "GPU Metrics"
56+
```shell
57+
---
58+
execute: now
59+
outputOnly: true
60+
---
61+
chart "$LOGDIR/resources/gpu.txt"
62+
```

plugins/plugin-codeflare/src/components/GPUChart.tsx

+125-92
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import React from "react"
18-
import { Chart, ChartAxis, ChartArea } from "@patternfly/react-charts"
18+
import { Chart, ChartAxis, ChartArea, ChartAreaProps, ChartAxisProps } from "@patternfly/react-charts"
1919
import { Log } from "../types"
2020

2121
import "../../web/scss/components/Dashboard/Charts.scss"
@@ -24,111 +24,144 @@ type Props = {
2424
logs: Log[]
2525
}
2626

27-
const generateXFormat = (logs: Log[]) => {
28-
return [
29-
...new Set(
30-
logs
31-
.filter((item) => item.utilizationGPU > 0)
32-
.map((item) => `${new Date(item.timestamp).getHours()}:${new Date(item.timestamp).getMinutes()}`)
33-
),
27+
export default class GPUChart extends React.PureComponent<Props> {
28+
private readonly colors = [
29+
"var(--color-base04)",
30+
"var(--color-latency-3)",
31+
"var(--color-latency-1)",
32+
"var(--color-latency-4)",
3433
]
35-
}
34+
private readonly labelColor = "var(--color-text-01)"
35+
private readonly fontFamily = "var(--font-sans-serif)"
3636

37-
const generateXValues = (logs: Log[]) => {
38-
return [...new Set(logs.filter((item) => item.utilizationGPU > 0).map((item) => item.timestamp))]
39-
}
37+
private readonly axisStyle: ChartAxisProps["style"] = {
38+
tickLabels: { fontSize: 9, fontFamily: this.fontFamily, fill: this.labelColor },
39+
axisLabel: { fontSize: 11, fontFamily: this.fontFamily, fill: this.labelColor },
40+
}
4041

41-
const axisStyle = { tickLabels: { fontSize: 9 } }
42-
const yTickValues = [0, 25, 50, 75, 100]
43-
const yTickLabels = yTickValues.map((_) => `${_}%`)
42+
private axisStyleWithGrid: ChartAxisProps["style"] = Object.assign({}, this.axisStyle, {
43+
grid: { strokeWidth: 1, stroke: this.colors[0] },
44+
})
4445

45-
const styles = {
46-
memory: { data: { fill: "var(--color-chart-0)" } },
47-
gpu: { data: { fill: "var(--color-chart-1)" } },
48-
}
46+
private readonly padding = {
47+
bottom: 25,
48+
left: 60,
49+
right: 25,
50+
top: 10,
51+
}
4952

50-
const GPUChart = (props: Props) => {
51-
const { logs } = props
52-
return (
53-
<div style={{ height: "auto", width: "100%", display: "flex", flexDirection: "column" }}>
54-
<Chart
55-
ariaTitle="GPU Utilization"
56-
ariaDesc="Chart showing GPU utilization over time"
57-
height={135}
58-
width={1000}
59-
maxDomain={{ y: 100 }}
60-
minDomain={{ y: 0 }}
61-
padding={{
62-
bottom: 25,
63-
left: 60,
64-
right: 5,
65-
top: 10,
66-
}}
67-
>
68-
<ChartAxis
69-
label="GPU"
70-
dependentAxis
71-
showGrid
72-
style={axisStyle}
73-
tickValues={yTickValues}
74-
tickFormat={yTickLabels}
75-
/>
76-
<ChartAxis
77-
scale="time"
78-
style={axisStyle}
79-
tickValues={generateXValues(logs)}
80-
tickFormat={generateXFormat(logs)}
81-
tickCount={generateXFormat(logs).length}
82-
/>
83-
<ChartArea
84-
style={styles.gpu}
85-
data={logs.map((log) => ({
86-
name: log.gpuType,
87-
x: log.timestamp,
88-
y: log.utilizationGPU,
89-
}))}
90-
/>
91-
</Chart>
53+
private readonly styles = {
54+
utilizationMemory: this.style(this.colors[1]),
55+
utilizationGPU: this.style(this.colors[2]),
56+
temperatureGPU: this.style(this.colors[3]),
57+
cluster: undefined,
58+
timestamp: undefined,
59+
gpuType: undefined,
60+
totalMemory: undefined,
61+
}
62+
63+
private readonly dimensions = {
64+
width: 400,
65+
height: 90,
66+
}
67+
68+
private readonly formatters = {
69+
celsius: (value: number) => value + "C",
70+
percentage: (value: number) => value + "%",
71+
timestamp: (timestamp: number) => `${new Date(timestamp).getHours()}:${new Date(timestamp).getMinutes()}`,
72+
}
73+
74+
private readonly minDomain = {
75+
percentage: { y: 0 },
76+
celsius: undefined,
77+
timestamp: undefined,
78+
}
79+
private readonly maxDomain = {
80+
percentage: { y: 100 },
81+
celsius: undefined,
82+
timestamp: undefined,
83+
}
84+
85+
private xAxis() {
86+
return (
87+
<ChartAxis
88+
scale="time"
89+
style={this.axisStyle}
90+
tickValues={this.props.logs.map((item) => item.timestamp)}
91+
tickFormat={this.formatters.timestamp}
92+
tickCount={5}
93+
/>
94+
)
95+
}
96+
97+
private style(color: string): ChartAreaProps["style"] {
98+
return { data: { stroke: color, fill: color } }
99+
}
100+
101+
private chart(
102+
title: string,
103+
desc: string,
104+
label: string,
105+
formatter: keyof typeof this.formatters,
106+
series: keyof Log,
107+
tickCount?: number
108+
) {
109+
return (
92110
<Chart
93-
ariaTitle="GPU Memory Utilization"
94-
ariaDesc="Chart showing GPU memory utilization over time"
95-
height={135}
96-
width={1000}
97-
maxDomain={{ y: 100 }}
98-
minDomain={{ y: 0 }}
99-
padding={{
100-
bottom: 25,
101-
left: 60,
102-
right: 5,
103-
top: 10,
104-
}}
111+
ariaTitle={title}
112+
ariaDesc={desc}
113+
height={this.dimensions.height}
114+
width={this.dimensions.width}
115+
maxDomain={this.maxDomain[formatter]}
116+
minDomain={this.minDomain[formatter]}
117+
padding={this.padding}
105118
>
106119
<ChartAxis
107-
label="Memory"
120+
label={label}
108121
dependentAxis
109-
showGrid
110-
style={axisStyle}
111-
tickValues={yTickValues}
112-
tickFormat={yTickLabels}
113-
/>
114-
<ChartAxis
115-
scale="time"
116-
style={axisStyle}
117-
tickValues={generateXValues(logs)}
118-
tickFormat={generateXFormat(logs)}
119-
tickCount={generateXFormat(logs).length}
122+
style={this.axisStyleWithGrid}
123+
tickFormat={this.formatters[formatter]}
124+
tickCount={tickCount}
120125
/>
126+
{this.xAxis()}
121127
<ChartArea
122-
style={styles.memory}
123-
data={logs.map((log) => ({
128+
style={this.styles[series]}
129+
data={this.props.logs.map((log) => ({
124130
name: log.gpuType,
125131
x: log.timestamp,
126-
y: log.utilizationMemory,
132+
y: log[series],
127133
}))}
128134
/>
129135
</Chart>
130-
</div>
131-
)
132-
}
136+
)
137+
}
133138

134-
export default GPUChart
139+
public render() {
140+
return (
141+
<div className="codeflare-chart-container">
142+
{this.chart(
143+
"GPU Utilization",
144+
"Chart showing GPU utilization over time",
145+
"Utilization",
146+
"percentage",
147+
"utilizationGPU"
148+
)}
149+
{this.chart(
150+
"GPU Memory Utilization",
151+
"Chart showing GPU memory utilization over time",
152+
"Memory",
153+
"percentage",
154+
"utilizationMemory"
155+
)}
156+
{this.chart(
157+
"GPU Temperature",
158+
"Chart showing GPU temperature over time",
159+
"Temperature",
160+
"celsius",
161+
"temperatureGPU",
162+
3
163+
)}
164+
</div>
165+
)
166+
}
167+
}

plugins/plugin-codeflare/src/controller/dashboard.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default function registerDashboardCommands(registrar: Registrar) {
5555
needsUI: true,
5656
outputOnly: true,
5757
flags,
58-
width: 1400,
59-
height: 1050,
58+
width: 1280,
59+
height: 960,
6060
})
6161
}

plugins/plugin-codeflare/web/scss/components/Dashboard/Charts.scss

+5-9
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
.pf-c-chart {
18-
line {
19-
stroke: var(--color-base01) !important;
20-
}
21-
22-
tspan {
23-
fill: var(--color-text-02) !important;
24-
font-family: var(--font-sans-serif) !important;
25-
}
17+
.codeflare-chart-container {
18+
height: auto;
19+
width: 100%;
20+
display: flex;
21+
flex-direction: column;
2622
}

plugins/plugin-codeflare/web/scss/components/Dashboard/_index.scss

+20-9
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@
2121
@import "@kui-shell/plugin-client-common/web/scss/components/Terminal/Maximized";
2222

2323
@include TopLevelTab {
24-
@include HasOnlyLeftStrip {
25-
@include Split(3) {
26-
@include Rows(5);
27-
@include Columns(5);
28-
grid-template-areas: "L1 L1 T1 T1 T1" "L1 L1 T1 T1 T1" "L1 L1 T1 T1 T1" "L1 L1 T1 T1 T1" "T2 T2 T2 T2 T2";
29-
}
24+
@include Split(3) {
25+
@include Rows(9);
26+
@include Columns(13);
27+
grid-template-areas: "T1 T1 T1 T1 T1 T2 T2 T2 T2 T2 T2 T2 T2" "T1 T1 T1 T1 T1 T2 T2 T2 T2 T2 T2 T2 T2" "T1 T1 T1 T1 T1 T2 T2 T2 T2 T2 T2 T2 T2" "T1 T1 T1 T1 T1 T2 T2 T2 T2 T2 T2 T2 T2" "T1 T1 T1 T1 T1 T2 T2 T2 T2 T2 T2 T2 T2" "T3 T3 T3 T3 T3 T2 T2 T2 T2 T2 T2 T2 T2" "T3 T3 T3 T3 T3 T2 T2 T2 T2 T2 T2 T2 T2" "T3 T3 T3 T3 T3 T2 T2 T2 T2 T2 T2 T2 T2" "T3 T3 T3 T3 T3 T2 T2 T2 T2 T2 T2 T2 T2";
3028
}
3129
}
3230

@@ -40,6 +38,21 @@
4038
grid-gap: 3px;
4139
}
4240

41+
@include Scrollback {
42+
&:not(.kui--inverted-color-context) {
43+
/* TODO MISSING SASS MIXIN in KUI CORE */
44+
@include Block {
45+
@include IsMaximized {
46+
@include CardBody {
47+
& > pre {
48+
background-color: var(--color-repl-background-02);
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
4356
@include Scrollback {
4457
.kui--ignore-if-empty {
4558
display: flex;
@@ -51,14 +64,12 @@
5164
flex: 1;
5265
}
5366
}
67+
5468
@include Block {
5569
@include IsMaximized {
5670
overflow: hidden;
5771

5872
@include CardBody {
59-
& > pre {
60-
background-color: var(--color-repl-background-02);
61-
}
6273
& > pre > .paragraph {
6374
@include Fill;
6475
}

0 commit comments

Comments
 (0)