Skip to content

Commit f6c3385

Browse files
committed
fix: clean up stats code a bit, display average in grid
1 parent 66eaae9 commit f6c3385

File tree

4 files changed

+94
-61
lines changed

4 files changed

+94
-61
lines changed

Diff for: plugins/plugin-codeflare-dashboard/src/components/Dashboard/Grid.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@
1515
*/
1616

1717
import React from "react"
18-
import { Box, BoxProps, Text, TextProps } from "ink"
18+
import { Box, BoxProps, Spacer, Text, TextProps } from "ink"
1919

2020
import type { Props, State } from "./index.js"
2121
import type { UpdatePayload, Worker } from "./types.js"
2222

23+
import { avg } from "./stats.js"
24+
2325
type GridProps = {
2426
/** Position of legend w.r.t. the grid UI [default: "below"] */
2527
legendPosition?: "right" | "below"
2628

29+
isQualitative: boolean
2730
scale: Props["scale"]
2831
title: NonNullable<Props["grids"][number]>["title"]
2932
states: NonNullable<Props["grids"][number]>["states"]
@@ -182,7 +185,19 @@ export default class Grid extends React.PureComponent<GridProps> {
182185
}
183186

184187
private title() {
185-
return <Text>{this.props.title}</Text>
188+
return (
189+
<React.Fragment>
190+
<Text>{this.props.title}</Text>
191+
{this.props.isQualitative ? (
192+
<React.Fragment />
193+
) : (
194+
<React.Fragment>
195+
<Spacer />
196+
<Text color="yellow">μ={avg(this.props.workers)}%</Text>
197+
</React.Fragment>
198+
)}
199+
</React.Fragment>
200+
)
186201
}
187202

188203
private get legendPosition() {

Diff for: plugins/plugin-codeflare-dashboard/src/components/Dashboard/Timeline.tsx

+4-59
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { Box, Text } from "ink"
1919

2020
import type { GridSpec, Worker } from "./types.js"
2121

22+
import { avg } from "./stats.js"
23+
2224
type Props = {
2325
gridModels: GridSpec[]
2426
workers: Worker[][]
@@ -51,63 +53,12 @@ export default class Timeline extends React.PureComponent<Props> {
5153
}, 0)
5254
}
5355

54-
/** @return the accumulated `total` and count `N` across a set of `workers` for the given `timeIdx` */
55-
private accum(workers: Worker[], timeIdx: number, field: "valueTotal" | "metricIdxTotal") {
56-
return workers.reduce(
57-
(A, worker) => {
58-
const history = worker.metricHistory
59-
if (history[timeIdx]) {
60-
A.total += history[timeIdx][field]
61-
A.N += history[timeIdx].N
62-
}
63-
return A
64-
},
65-
{ total: 0, N: 0 }
66-
)
67-
}
68-
69-
/** @return average metric value across a set of `workers` for the given `timeIdx` */
70-
private avg(workers: Worker[], timeIdx: number, field: "valueTotal" | "metricIdxTotal"): number {
71-
const { total, N } = this.accum(workers, timeIdx, field)
72-
if (N === 0) {
73-
if (timeIdx === 0) return 0
74-
else {
75-
for (let t = timeIdx - 1; t >= 0; t--) {
76-
const { total, N } = this.accum(workers, t, field)
77-
if (N !== 0) {
78-
return Math.round(total / N)
79-
}
80-
}
81-
return 0
82-
}
83-
}
84-
85-
return Math.round(total / N)
86-
}
87-
88-
/** @return long-term average, averaged over time and across a set of `workers` */
89-
private longTermAvg(workers: Worker[], nTimes: number) {
90-
const { total, N } = Array(nTimes)
91-
.fill(0)
92-
.map((_, timeIdx) => this.accum(workers, timeIdx, "valueTotal"))
93-
.reduce(
94-
(A, { total, N }) => {
95-
A.total += total
96-
A.N += N
97-
return A
98-
},
99-
{ total: 0, N: 0 }
100-
)
101-
102-
return Math.round(total / N)
103-
}
104-
10556
/**
10657
* Render one cell to represent the average over the given `workers`
10758
* for the given grid, for the given time.
10859
*/
10960
private cell(workers: Worker[], spec: GridSpec, timeIdx: number, isLatest: boolean) {
110-
const metricIdx = this.avg(workers, timeIdx, "metricIdxTotal")
61+
const metricIdx = avg(workers, "metricIdxTotal", timeIdx)
11162
const style = spec.states[metricIdx] ? spec.states[metricIdx].style : { color: "gray", dimColor: true }
11263

11364
return (
@@ -135,12 +86,6 @@ export default class Timeline extends React.PureComponent<Props> {
13586
<Text>{spec.title.padStart(this.maxLabelLength)}</Text>
13687
</Box>
13788
<Box marginLeft={1}>{this.cells(workers, spec, nTimes, timeStartIdx)}</Box>
138-
<Text>
139-
{Math.round(this.avg(workers, nTimes - 1, "valueTotal"))
140-
.toFixed()
141-
.padStart(3) + "%"}
142-
</Text>
143-
<Text color="yellow"> μ={Math.round(this.longTermAvg(workers, nTimes)) + "%"}</Text>
14489
</React.Fragment>
14590
)
14691
}
@@ -155,7 +100,7 @@ export default class Timeline extends React.PureComponent<Props> {
155100

156101
// to help us compute whether we are about to overflow terminal width
157102
const maxLabelLength = this.props.gridModels.reduce((N, spec) => {
158-
return Math.max(N, "100% μ=100%".length + spec.title.length)
103+
return Math.max(N, spec.title.length)
159104
}, 0)
160105

161106
// once we overflow, display the suffix of history information, starting at this index

Diff for: plugins/plugin-codeflare-dashboard/src/components/Dashboard/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ export default class Dashboard extends React.PureComponent<Props, State> {
235235
title={grid.title}
236236
scale={this.props.scale}
237237
states={grid.states}
238+
isQualitative={grid.isQualitative}
238239
workers={this.state?.workers[widx] || []}
239240
legendPosition={row.length === 1 ? "right" : "below"}
240241
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 { Worker } from "./types.js"
18+
19+
/** @return the accumulated `total` and count `N` across a set of `workers` for the given `timeIdx` */
20+
function accum(workers: Worker[], timeIdx: number, field: "valueTotal" | "metricIdxTotal") {
21+
return workers.reduce(
22+
(A, worker) => {
23+
const history = worker.metricHistory
24+
if (history[timeIdx]) {
25+
A.total += history[timeIdx][field]
26+
A.N += history[timeIdx].N
27+
}
28+
return A
29+
},
30+
{ total: 0, N: 0 }
31+
)
32+
}
33+
34+
/** @return average metric value across a set of `workers` for the given `timeIdx` */
35+
export function avg(
36+
workers: Worker[],
37+
field: "valueTotal" | "metricIdxTotal" = "valueTotal",
38+
timeIdx = workers.reduce((M, _) => Math.max(M, _.metricHistory.length), 0)
39+
): number {
40+
const { total, N } = accum(workers, timeIdx, field)
41+
if (N === 0) {
42+
if (timeIdx === 0) return 0
43+
else {
44+
for (let t = timeIdx - 1; t >= 0; t--) {
45+
const { total, N } = accum(workers, t, field)
46+
if (N !== 0) {
47+
return Math.round(total / N)
48+
}
49+
}
50+
return 0
51+
}
52+
}
53+
54+
return Math.round(total / N)
55+
}
56+
57+
/** @return long-term average, averaged over time and across a set of `workers` */
58+
export function longTermAvg(workers: Worker[], nTimes: number) {
59+
const { total, N } = Array(nTimes)
60+
.fill(0)
61+
.map((_, timeIdx) => accum(workers, timeIdx, "valueTotal"))
62+
.reduce(
63+
(A, { total, N }) => {
64+
A.total += total
65+
A.N += N
66+
return A
67+
},
68+
{ total: 0, N: 0 }
69+
)
70+
71+
return Math.round(total / N)
72+
}

0 commit comments

Comments
 (0)