Skip to content

Commit 8a3cdb3

Browse files
committed
fix: dashboard should not show gpu grids for runs not using gpus
1 parent 0c4c94c commit 8a3cdb3

File tree

4 files changed

+125
-19
lines changed

4 files changed

+125
-19
lines changed

Diff for: plugins/plugin-codeflare-dashboard/src/controller/dashboard/index.ts

+38-16
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,42 @@ export async function jobIdFrom(args: Arguments<Options>, cmd: string, offset =
8181
return { jobId, profile }
8282
}
8383

84+
/** @return grid model for the given `kind` for `jobId` in `profile` */
85+
async function gridFor(
86+
kind: SupportedGrid,
87+
profile: string,
88+
jobId: string,
89+
opts: Pick<Options, "demo" | "theme">
90+
): Promise<GridSpec> {
91+
const tails = await tailf(kind, profile, jobId)
92+
return kind === "status"
93+
? status(tails, { demo: opts.demo, theme: opts.theme, themeDefault: "colorbrewer" })
94+
: utilization(kind, tails, opts)
95+
}
96+
97+
/** @return all relevant grid models for `jobId` in `profile` */
98+
async function allGridsFor(profile: string, jobId: string, opts: Pick<Options, "demo" | "theme">) {
99+
const usesGpus = opts.demo || (await import("../env.js").then((_) => _.usesGpus(profile, jobId)))
100+
101+
const all = [
102+
gridFor("status", profile, jobId, opts),
103+
null, // newline
104+
gridFor("cpu%", profile, jobId, opts),
105+
]
106+
107+
if (usesGpus) {
108+
all.push(gridFor("gpu%", profile, jobId, opts))
109+
}
110+
111+
all.push(gridFor("mem%", profile, jobId, opts))
112+
113+
if (usesGpus) {
114+
all.push(gridFor("gpumem%", profile, jobId, opts))
115+
}
116+
117+
return Promise.all(all)
118+
}
119+
84120
export default async function dashboard(args: Arguments<Options>, cmd: "db" | "dashboard") {
85121
const { theme } = args.parsedOptions
86122

@@ -98,25 +134,11 @@ export default async function dashboard(args: Arguments<Options>, cmd: "db" | "d
98134
throw new Error(usage(cmd, ["all"]))
99135
}
100136

101-
const gridFor = async (kind: SupportedGrid): Promise<GridSpec> => {
102-
const tails = await tailf(kind, profile, jobId)
103-
return kind === "status"
104-
? status(tails, { demo, theme, themeDefault: "colorbrewer" })
105-
: utilization(kind, tails, { demo, theme })
106-
}
107-
108137
const gridForA = async (kind: KindA): Promise<null | GridSpec | (null | GridSpec)[]> => {
109138
if (kind === "all") {
110-
return Promise.all([
111-
gridFor("status"),
112-
null, // newline
113-
gridFor("cpu%"),
114-
gridFor("gpu%"),
115-
gridFor("mem%"),
116-
gridFor("gpumem%"),
117-
])
139+
return allGridsFor(profile, jobId, { demo, theme })
118140
} else if (isSupportedGrid(kind)) {
119-
return gridFor(kind)
141+
return gridFor(kind, profile, jobId, { demo, theme })
120142
} else {
121143
return null
122144
}

Diff for: plugins/plugin-codeflare-dashboard/src/controller/dump.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,18 @@ export default async function dump(args: Arguments<Options>) {
4242
// and for which jobId and profile?
4343
const { jobId, profile } = await jobIdFrom(args, "dump")
4444

45-
if (!(isValidKind(kind) || kind === "path")) {
45+
if (!(isValidKind(kind) || kind === "path" || kind === "env")) {
4646
throw new Error(usage())
4747
} else if (!jobId) {
4848
throw new Error(usage())
4949
}
5050

5151
if (kind === "path") {
5252
// print the path to the data captured for the given jobId in the given profile
53-
const { dirname } = await import("path")
54-
return Array.from(new Set(await pathsFor("cpu%", profile, jobId).then((_) => _.map((_) => dirname(dirname(_))))))[0]
53+
return import("./path.js").then((_) => _.pathFor(profile, jobId))
54+
} else if (kind === "env") {
55+
// print job env vars
56+
return JSON.stringify(await import("./env.js").then((_) => _.getJobEnv(profile, jobId)), undefined, 2)
5557
} else if (!args.parsedOptions.f) {
5658
const { createReadStream } = await import("fs")
5759
await Promise.all(
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 { join } from "path"
18+
19+
type NameValue = { name: string; value: unknown }
20+
21+
function isNameValue(obj: object): obj is NameValue {
22+
const nv = obj as NameValue
23+
return typeof nv === "object" && typeof nv.name === "string" && typeof nv.value !== undefined
24+
}
25+
26+
function isNameValueArray(obj: object): obj is NameValue[] {
27+
const nva = obj as NameValue[]
28+
return Array.isArray(nva) && nva.every(isNameValue)
29+
}
30+
31+
function toRecord(nva: NameValue[]): Record<string, unknown> {
32+
return nva.reduce((R, { name, value }) => {
33+
R[name] = value
34+
return R
35+
}, {} as Record<string, unknown>)
36+
}
37+
38+
export async function getJobEnv(profile: string, jobId: string): Promise<Record<string, unknown>> {
39+
const filepath = await import("./path.js").then((_) => _.pathFor(profile, jobId))
40+
const nameValueArray = JSON.parse(
41+
await import("fs/promises").then((_) => _.readFile(join(filepath, "env.json"))).then((_) => _.toString())
42+
)
43+
if (!isNameValueArray(nameValueArray)) {
44+
throw new Error("Malformatted env.json file")
45+
} else {
46+
return toRecord(nameValueArray)
47+
}
48+
}
49+
50+
export async function numGpus(profile: string, jobId: string): Promise<number> {
51+
const env = await getJobEnv(profile, jobId)
52+
53+
const raw = env["NUM_GPUS"]
54+
return typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : 0
55+
}
56+
57+
export async function usesGpus(profile: string, jobId: string): Promise<boolean> {
58+
return (await numGpus(profile, jobId)) > 0
59+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 { pathsFor } from "./dashboard/tailf.js"
18+
19+
/** @return path to the data captured for the given jobId in the given profile */
20+
export async function pathFor(profile: string, jobId: string) {
21+
const { dirname } = await import("path")
22+
return Array.from(new Set(await pathsFor("cpu%", profile, jobId).then((_) => _.map((_) => dirname(dirname(_))))))[0]
23+
}

0 commit comments

Comments
 (0)