Skip to content

Commit 6b4b82e

Browse files
committed
fix: charts may have different y axis labels
we need to use a common time range (min, max) across the charts
1 parent 3b913aa commit 6b4b82e

File tree

7 files changed

+92
-67
lines changed

7 files changed

+92
-67
lines changed

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

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,14 @@ export type BaseChartProps = Pick<ChartProps, "domain" | "padding"> & {
5959
)[]
6060
}
6161

62-
interface Props {
62+
export type TimeRange = {
63+
timeRange: {
64+
min: number
65+
max: number
66+
}
67+
}
68+
69+
type Props = TimeRange & {
6370
charts: BaseChartProps[]
6471
}
6572

@@ -172,27 +179,16 @@ export default class BaseChart extends React.PureComponent<Props> {
172179
}
173180

174181
private xAxis() {
175-
// TODO, factor this out into a State variable?
176-
const { min, max } = this.props.charts.reduce(
177-
(M, chart) =>
178-
chart.series.reduce(
179-
(M, series) =>
180-
series.data.reduce((M, point) => {
181-
M.min = Math.min(M.min, point.x)
182-
M.max = Math.max(M.max, point.x)
183-
return M
184-
}, M),
185-
M
186-
),
187-
{ min: Number.MAX_VALUE, max: Number.MIN_VALUE }
188-
)
182+
// timestamps in the Series (i.e. datum.x values) are assumed to
183+
// be relativized to the given minTimestamp
184+
const range = this.props.timeRange.max - this.props.timeRange.min
189185

190186
return (
191187
<ChartAxis
192188
scale="time"
193189
style={BaseChart.axisStyle}
194190
tickFormat={BaseChart.formatters.timestamp}
195-
tickValues={[(max - min) / 4, max]}
191+
tickValues={[range / 4, range]}
196192
/>
197193
)
198194
}

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import React from "react"
1818

1919
import { Log } from "../controller/charts/gpu"
20-
import BaseChart, { BaseChartProps } from "./Chart"
2120
import { HostMap } from "../controller/charts/LogRecord"
21+
import BaseChart, { BaseChartProps, TimeRange } from "./Chart"
2222

23-
type Props = {
23+
type Props = TimeRange & {
2424
logs: HostMap<Log>
2525
}
2626

@@ -41,27 +41,22 @@ export default class GPUChart extends React.PureComponent<Props, State> {
4141
}
4242

4343
private static charts(props: Props): BaseChartProps[] {
44-
const earliestTimestamp: number = Object.values(props.logs).reduce(
45-
(min, logs) => logs.reduce((min, line) => Math.min(min, line.timestamp), Number.MAX_VALUE),
46-
Number.MAX_VALUE
47-
)
48-
4944
return Object.entries(props.logs).map(([node, lines]) => {
5045
const d1 = lines.map((line) => ({
5146
name: BaseChart.nodeNameLabel(node) + " GPU Utilization",
52-
x: line.timestamp - earliestTimestamp,
47+
x: line.timestamp - props.timeRange.min,
5348
y: line.utilizationGPU,
5449
}))
5550

5651
const d2 = lines.map((line) => ({
5752
name: BaseChart.nodeNameLabel(node) + " GPU Memory Utilization",
58-
x: line.timestamp - earliestTimestamp,
53+
x: line.timestamp - props.timeRange.min,
5954
y: line.utilizationMemory,
6055
}))
6156

6257
const d3 = lines.map((line) => ({
6358
name: BaseChart.nodeNameLabel(node) + " GPU Temperature",
64-
x: line.timestamp - earliestTimestamp,
59+
x: line.timestamp - props.timeRange.min,
6560
y: line.temperatureGPU,
6661
}))
6762

@@ -113,6 +108,6 @@ export default class GPUChart extends React.PureComponent<Props, State> {
113108
}
114109

115110
public render() {
116-
return <BaseChart charts={this.state.charts} />
111+
return <BaseChart charts={this.state.charts} timeRange={this.props.timeRange} />
117112
}
118113
}

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import React from "react"
1818

1919
import { Log } from "../controller/charts/vmstat"
20-
import BaseChart, { BaseChartProps } from "./Chart"
2120
import { HostMap } from "../controller/charts/LogRecord"
21+
import BaseChart, { BaseChartProps, TimeRange } from "./Chart"
2222

23-
type Props = {
23+
type Props = TimeRange & {
2424
logs: HostMap<Log>
2525
}
2626

@@ -29,10 +29,6 @@ type State = {
2929
}
3030

3131
export default class VmstatChart extends React.PureComponent<Props, State> {
32-
private static readonly padding = Object.assign({}, BaseChart.padding, {
33-
right: BaseChart.padding.left,
34-
})
35-
3632
public constructor(props: Props) {
3733
super(props)
3834
this.state = {
@@ -41,21 +37,16 @@ export default class VmstatChart extends React.PureComponent<Props, State> {
4137
}
4238

4339
private static charts(props: Props): BaseChartProps[] {
44-
const earliestTimestamp: number = Object.values(props.logs).reduce(
45-
(min, logs) => logs.reduce((min, line) => Math.min(min, line.timestamp), Number.MAX_VALUE),
46-
Number.MAX_VALUE
47-
)
48-
4940
return Object.entries(props.logs).map(([node, lines]) => {
5041
const d1 = lines.map((line) => ({
5142
name: BaseChart.nodeNameLabel(node) + " CPU Utilization",
52-
x: line.timestamp - earliestTimestamp,
43+
x: line.timestamp - props.timeRange.min,
5344
y: 100 - line.idle,
5445
}))
5546

5647
const d2 = lines.map((line) => ({
5748
name: BaseChart.nodeNameLabel(node) + " Free Memory",
58-
x: line.timestamp - earliestTimestamp,
49+
x: line.timestamp - props.timeRange.min,
5950
y: line.freeMemory,
6051
}))
6152

@@ -70,7 +61,6 @@ export default class VmstatChart extends React.PureComponent<Props, State> {
7061
title: BaseChart.nodeNameLabel(node),
7162
desc: "Chart showing CPU utilization over time for " + node,
7263
series,
73-
padding: VmstatChart.padding,
7464
yAxes: [
7565
{
7666
label: "CPU",
@@ -95,6 +85,6 @@ export default class VmstatChart extends React.PureComponent<Props, State> {
9585
}
9686

9787
public render() {
98-
return <BaseChart charts={this.state.charts} />
88+
return <BaseChart charts={this.state.charts} timeRange={this.props.timeRange} />
9989
}
10090
}

plugins/plugin-codeflare/src/controller/charts/all.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import React from "react"
1818
import { join } from "path"
1919
import { Arguments } from "@kui-shell/core"
2020

21+
import { toHostMap } from "./LogRecord"
22+
import { timeRange } from "./timestamps"
23+
2124
/** Oops, sometimes we have no data for a give node */
2225
function noData(node: string, kind: "CPU Utilization" | "GPU Utilization") {
2326
return (
@@ -52,32 +55,42 @@ export default async function all(args: Arguments) {
5255
])
5356

5457
// get a canonical list of nodes
55-
const nodes = Array.from(new Set(Object.keys(gpuData).concat(Object.keys(cpuData)))).sort((a, b) => {
58+
const gpuMap = toHostMap(gpuData)
59+
const cpuMap = toHostMap(cpuData)
60+
const nodes = Array.from(new Set(Object.keys(gpuMap).concat(Object.keys(cpuMap)))).sort((a, b) => {
5661
// sort them so that nodes for which we have both gpu and cpu
5762
// data float to the top; in second place will be the group of
5863
// nodes for which we have only gpu data; in last place will be
5964
// the nodes for which we only have cpu data
60-
const aHasG = gpuData[a]
61-
const aHasC = cpuData[a]
62-
const bHasG = gpuData[b]
63-
const bHasC = cpuData[b]
65+
const aHasG = gpuMap[a]
66+
const aHasC = cpuMap[a]
67+
const bHasG = gpuMap[b]
68+
const bHasC = cpuMap[b]
6469

6570
// 2 vs 1 to get the gpu-first priority described above
6671
const vA = (aHasG ? 2 : 0) + (aHasC ? 1 : 0)
6772
const vB = (bHasG ? 2 : 0) + (bHasC ? 1 : 0)
6873
return vB - vA
6974
})
7075

71-
const linearized = nodes.map((node) => {
72-
const gpuForNode = gpuData[node]
73-
const cpuForNode = cpuData[node]
74-
return [
75-
!gpuForNode ? noData(node, "GPU Utilization") : <GPUChart logs={{ [node]: gpuForNode }} />,
76-
!cpuForNode ? noData(node, "CPU Utilization") : <VmstatChart logs={{ [node]: cpuData[node] }} />,
77-
]
78-
})
76+
const range = timeRange(gpuData, cpuData)
77+
78+
const linearized = nodes
79+
.map((node) => {
80+
const gpuForNode = gpuMap[node]
81+
const cpuForNode = cpuMap[node]
82+
return [
83+
!gpuForNode ? noData(node, "GPU Utilization") : <GPUChart timeRange={range} logs={{ [node]: gpuForNode }} />,
84+
!cpuForNode ? (
85+
noData(node, "CPU Utilization")
86+
) : (
87+
<VmstatChart timeRange={range} logs={{ [node]: cpuMap[node] }} />
88+
),
89+
]
90+
})
91+
.flatMap((_) => _)
7992

8093
return {
81-
react: <div className="codeflare-chart-grid flex-fill">{linearized.flatMap((_) => _)}</div>,
94+
react: <div className="codeflare-chart-grid flex-fill">{linearized}</div>,
8295
}
8396
}

plugins/plugin-codeflare/src/controller/charts/gpu.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import stripAnsi from "strip-ansi"
1919
import { Arguments } from "@kui-shell/core"
2020

2121
import { expand } from "../../lib/util"
22+
import { timeRange } from "./timestamps"
2223
import LogRecord, { toHostMap } from "./LogRecord"
2324

2425
import GPUChart from "../../components/GPUChart"
@@ -63,14 +64,14 @@ function formatLogObject(logLine: string[]) {
6364
export async function parse(filepath: string, REPL: Arguments["REPL"]) {
6465
const logs = stripAnsi(await REPL.qexec<string>(`vfs fslice ${expand(filepath)} 0`))
6566
const formattedLogs = formatLogs(logs)
66-
return toHostMap(formattedLogs.map((logLine) => formatLogObject(logLine)))
67+
return formattedLogs.map((logLine) => formatLogObject(logLine))
6768
}
6869

6970
export function chart(logs: Awaited<ReturnType<typeof parse>>) {
7071
return {
7172
react: (
7273
<ChartGrid>
73-
<GPUChart logs={logs} />
74+
<GPUChart logs={toHostMap(logs)} timeRange={timeRange(logs)} />
7475
</ChartGrid>
7576
),
7677
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2022 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 LogRecord from "./LogRecord"
18+
19+
export function timeRange(...records: LogRecord<unknown>[][]) {
20+
const min = records.reduce(
21+
(min, logs) => logs.reduce((min, line) => Math.min(min, line.timestamp), Number.MAX_VALUE),
22+
Number.MAX_VALUE
23+
)
24+
25+
const max = records.reduce(
26+
(max, logs) => logs.reduce((max, line) => Math.max(max, line.timestamp), Number.MIN_VALUE),
27+
Number.MIN_VALUE
28+
)
29+
30+
return { min, max }
31+
}

plugins/plugin-codeflare/src/controller/charts/vmstat.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import React from "react"
1818
import { Arguments } from "@kui-shell/core"
1919

2020
import { expand } from "../../lib/util"
21+
import { timeRange } from "./timestamps"
2122
import LogRecord, { toHostMap } from "./LogRecord"
2223

2324
import ChartGrid from "../../components/ChartGrid"
@@ -53,21 +54,19 @@ function parseLine(cells: string[]): Log {
5354
}
5455

5556
export async function parse(filepath: string, REPL: Arguments["REPL"]) {
56-
return toHostMap(
57-
(await REPL.qexec<string>(`vfs fslice ${expand(filepath)} 0`))
58-
.split(/\n/)
59-
.filter((logLine) => logLine && !/----|swpd/.test(logLine))
60-
.map((_) => _.split(/\s+/))
61-
.map(parseLine)
62-
.sort((a, b) => a.hostname.localeCompare(b.hostname))
63-
)
57+
return (await REPL.qexec<string>(`vfs fslice ${expand(filepath)} 0`))
58+
.split(/\n/)
59+
.filter((logLine) => logLine && !/----|swpd/.test(logLine))
60+
.map((_) => _.split(/\s+/))
61+
.map(parseLine)
62+
.sort((a, b) => a.hostname.localeCompare(b.hostname))
6463
}
6564

6665
export function chart(logs: Awaited<ReturnType<typeof parse>>) {
6766
return {
6867
react: (
6968
<ChartGrid>
70-
<VmstatChart logs={logs} />
69+
<VmstatChart logs={toHostMap(logs)} timeRange={timeRange(logs)} />
7170
</ChartGrid>
7271
),
7372
}

0 commit comments

Comments
 (0)