Skip to content

Commit

Permalink
feat: new console-based dashboard
Browse files Browse the repository at this point in the history
`codeflare dashboard` command

```shell
codeflare dashboard all|cpu|memory|status
```

this currently assumes that the aggregator is already running against the job
no gpu metrics, yet

This also adds some helpers:

```
codeflare dump logs|cpu|gpu|memory|status
```
  • Loading branch information
starpit committed Mar 30, 2023
1 parent f89e4e2 commit 95e1919
Show file tree
Hide file tree
Showing 29 changed files with 2,816 additions and 1,410 deletions.
2,496 changes: 1,106 additions & 1,390 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"@kui-shell/plugin-bash-like": "13.1.3-dev-20230329-123301",
"@kui-shell/plugin-client-common": "13.1.3-dev-20230329-123301",
"@kui-shell/plugin-codeflare": "file:./plugins/plugin-codeflare",
"@kui-shell/plugin-codeflare-dashboard": "file:./plugins/plugin-codeflare-dashboard",
"@kui-shell/plugin-core-support": "13.1.3-dev-20230329-123301",
"@kui-shell/plugin-electron-components": "13.1.3-dev-20230329-123301",
"@kui-shell/plugin-kubectl": "13.1.3-dev-20230329-123301",
Expand Down
2 changes: 2 additions & 0 deletions plugins/plugin-codeflare-dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
mdist
6 changes: 6 additions & 0 deletions plugins/plugin-codeflare-dashboard/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build
src
tests
dist/test
tsconfig*
/*.tgz
37 changes: 37 additions & 0 deletions plugins/plugin-codeflare-dashboard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@kui-shell/plugin-codeflare-dashboard",
"version": "0.0.1",
"description": "ML dashboarding",
"types": "./mdist/index.d.ts",
"module": "./mdist/index.js",
"main": "./mdist/index.js",
"license": "Apache-2.0",
"homepage": "https://github.com/IBM/kui#readme",
"bugs": {
"url": "https://github.com/IBM/kui/issues/new"
},
"repository": {
"type": "git",
"url": "git+https://github.com/IBM/kui.git"
},
"keywords": [
"kui",
"plugin"
],
"private": true,
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/split2": "^3.2.1",
"react-devtools-core": "^4.27.2"
},
"dependencies": {
"@logdna/tail-file": "^3.0.1",
"chokidar": "^3.5.3",
"ink": "^3.2.0",
"madwizard": "^7.0.3",
"pretty-ms": "^7.0.1",
"strip-ansi": "6.0.0"
}
}
133 changes: 133 additions & 0 deletions plugins/plugin-codeflare-dashboard/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright 2023 The Kubernetes Authors
*
* 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.
*/

import React from "react"
import { Box, Text, TextProps } from "ink"
import type { Arguments } from "@kui-shell/core"

type WorkerState = "Pending" | "Scheduled" | "Installing" | "Running" | "Failed" | "Success"

type Props = unknown
type State = {
workers: WorkerState[]
}

class Dashboard extends React.PureComponent<Props, State> {
public constructor(props: Props) {
super(props)

const styles = Object.keys(this.styleOf) as WorkerState[]
const randoState = () => styles[Math.round(Math.random() * styles.length) % styles.length]
this.state = {
workers: Array(50).fill(1).map(randoState),
}

setInterval(() => {
this.setState((curState) => {
const idx = Math.round(Math.random() * this.state.workers.length) % this.state.workers.length
const styles = Object.keys(this.styleOf)
const newState = styles[Math.round(Math.random() * styles.length) % styles.length]
return {
workers: [...curState.workers.slice(0, idx), newState as WorkerState, ...curState.workers.slice(idx + 1)],
}
})
}, 1000)
}

// private readonly sizes = ["▁▁", "▃▃", "▅▅", "▆▆", "██", "■■"]
// private readonly sizes = "■".repeat(5)
private readonly sizes = Array(5).fill("▇▇")
// private readonly sizes = "█".repeat(4)

private readonly styleOf: Record<WorkerState, TextProps> = {
Pending: { color: "gray", children: this.sizes[3] },
Scheduled: { color: "white", dimColor: true, children: this.sizes[3] },
// Pulling: { color: "yellow", dimColor: true, children: this.sizes[3] },
Installing: { color: "yellow", children: this.sizes[3] },
Running: { color: "cyan", children: this.sizes[4] },
Failed: { color: "red", children: this.sizes[4] },
Success: { color: "blue", children: this.sizes[4] },
}

private matrix(): WorkerState[][] {
const N = Math.ceil(Math.sqrt(this.state.workers.length))
const matrix = Array(N)
for (let i = 0; i < N; i++) {
matrix[i] = Array(N)
for (let j = 0; j < N; j++) {
matrix[i][j] = this.state.workers[i * N + j]
}
}
return matrix
}

private histo(): number[] {
const keys = Object.keys(this.styleOf)
const indexer = keys.reduce((M, worker, idx) => {
M[worker] = idx
return M
}, {} as Record<string, number>)

return this.state.workers.reduce((H, worker) => {
H[indexer[worker]]++
return H
}, Array(keys.length).fill(0))
}

public render() {
const M = this.matrix()
const H = this.histo()

return (
<Box flexDirection="column" margin={1}>
<Box>
{Object.keys(this.styleOf).map((_, idx) => (
<Box width="20%" borderStyle="singleDouble" marginRight={1} key={_}>
<Box flexDirection="column">
<Box>
<Text color={this.styleOf[_ as WorkerState].color} bold inverse>
{_}
</Text>
</Box>
<Box>
<Text>{H[idx]}</Text>
</Box>
</Box>
</Box>
))}
</Box>

<Box marginTop={1} width={M.reduce((N, c) => (N += c.length), 0)} flexDirection="column">
{M.map((row, ridx) => (
<Box key={ridx}>
{row.map((worker, cidx) => (
<Text key={cidx} {...this.styleOf[worker]} />
))}
</Box>
))}
</Box>
</Box>
)
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default async function dashboard(args: Arguments) {
const { render } = await import("ink")
await render(<Dashboard />)
await new Promise(() => {}) // eslint-disable-line @typescript-eslint/no-empty-function
return true
}
Loading

0 comments on commit 95e1919

Please sign in to comment.