Skip to content

Commit e5dda5c

Browse files
authored
[Utilizaiton] Add RankTable View (#6274)
- add rank table view for choosing the single test - calculate all gpus' mem and util avg,max to show in rank and stats table - able to cick on test to show on rank too ## Demo https://torchci-1fhqvklex-fbopensource.vercel.app/utilization/13143119394/36681447812/1 ## Select single test based on rank chart: ![rabk1gif](https://github.com/user-attachments/assets/6dc796de-ced0-4380-9124-78e52baddd72) ## Sync selected test on test view and rank chart for easy tracking. ![rank2](https://github.com/user-attachments/assets/5d2b7b13-e076-4b2d-9854-cbf6900ff1df)
1 parent 3ffcfdf commit e5dda5c

File tree

11 files changed

+669
-147
lines changed

11 files changed

+669
-147
lines changed

torchci/components/utilization/UtilizationPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getIgnoredSegmentName, processStatsData } from "./helper";
1212
import { Divider, MainPage, Section } from "./styles";
1313
import { StatsInfo } from "./types";
1414

15-
const lineFilters: PickerConfig[] = [
15+
export const lineFilters: PickerConfig[] = [
1616
{ category: "all", types: [{ name: "all", tags: ["|"] }] },
1717
{
1818
category: "gpu",
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import * as echarts from "echarts";
2+
import { truncate } from "lodash";
3+
import { useEffect, useRef, useState } from "react";
4+
5+
export function RankBar({
6+
onRankClick = () => {},
7+
selectedId,
8+
data,
9+
resourceName,
10+
statType,
11+
}: {
12+
onRankClick?: (rank: string) => void;
13+
selectedId?: string | null;
14+
data: { name: string; resourceName: string; [key: string]: any }[];
15+
resourceName: string;
16+
statType: string;
17+
}) {
18+
const chartRef = useRef(null); // Create a ref for the chart container
19+
const baseHeight = 20; // Height per item
20+
const minHeight = 300; // Minimum height for small datasets
21+
const maxHeight = 600; // Maximum height for very large datasets
22+
23+
const [chartInstance, setChartInstance] = useState<any>(null);
24+
const [chartData, setChartData] = useState<any[]>([]);
25+
26+
const handleClick = (params: any) => {
27+
if (params.componentType === "yAxis") {
28+
onRankClick(params.value);
29+
} else if (params.componentType == "series") {
30+
onRankClick(params.value[2]);
31+
}
32+
};
33+
34+
useEffect(() => {
35+
if (data.length == 0) {
36+
return;
37+
}
38+
39+
let echartData: any[][] = [];
40+
let validItems = data
41+
.filter((item) => {
42+
return item.resourceName === resourceName;
43+
})
44+
.sort((a, b) => {
45+
return a[statType] - b[statType];
46+
});
47+
48+
validItems.map((item) => {
49+
echartData.push([item[statType], item[statType], item.name]);
50+
});
51+
52+
setChartData(echartData);
53+
}, [data, statType, resourceName]);
54+
55+
useEffect(() => {
56+
if (chartData.length == 0) {
57+
return;
58+
}
59+
60+
let instance = chartInstance;
61+
if (!instance) {
62+
instance = echarts.init(chartRef.current);
63+
setChartInstance(chartInstance);
64+
}
65+
66+
const options: echarts.EChartOption = getOptions(chartData, selectedId);
67+
68+
instance.setOption(options, { notMerge: true });
69+
instance.on("click", handleClick);
70+
return () => {
71+
instance.dispose();
72+
};
73+
}, [chartData, selectedId]);
74+
75+
if (chartData.length == 0) {
76+
return <div></div>;
77+
}
78+
79+
return (
80+
<div
81+
ref={chartRef}
82+
style={{
83+
height: Math.min(
84+
maxHeight,
85+
Math.max(minHeight, chartData.length * baseHeight)
86+
),
87+
}}
88+
/>
89+
);
90+
}
91+
92+
const getOptions = (data: any[], selectedId: any): any => {
93+
return {
94+
animation: false,
95+
dataset: {
96+
source: [["score", "percent", "test"], ...data],
97+
},
98+
grid: { containLabel: true },
99+
tooltip: {
100+
trigger: "axis", // Show tooltip when hovering on the axis
101+
formatter: function (params: any) {
102+
let yValue = params[0].value; // Get full Y-axis value
103+
return `${yValue[2]}:<br> ${yValue[0]}%`; // Show full value in tooltip
104+
},
105+
},
106+
xAxis: { name: "percent" },
107+
yAxis: {
108+
type: "category",
109+
triggerEvent: true,
110+
axisLabel: {
111+
interval: 0,
112+
color: function (value: any, index: any) {
113+
if (value === selectedId) {
114+
return "blue"; // Highlight only clicked item
115+
}
116+
return "#333"; // Default color for other items
117+
},
118+
formatter: function (value: any, index: any) {
119+
if (value.length > 100) {
120+
return truncate(value, { length: 100 }) + "..."; // Truncate long strings
121+
}
122+
return value;
123+
},
124+
},
125+
},
126+
visualMap: {
127+
type: "continuous",
128+
orient: "horizontal",
129+
left: "center",
130+
min: 0,
131+
max: 100,
132+
text: ["High Usage", "Low Usage"],
133+
// Map the score column to color
134+
dimension: 0,
135+
inRange: {
136+
color: ["#65B581", "#FFCE34", "#FD665F"],
137+
},
138+
},
139+
series: [
140+
{
141+
label: {
142+
show: true,
143+
position: "inside",
144+
color: "black",
145+
formatter: function (params: any) {
146+
if (params.value[0] <= 1) {
147+
return ""; // Hide labels for small values
148+
}
149+
return params.value[0] + "%";
150+
},
151+
},
152+
type: "bar",
153+
encode: {
154+
x: "percent",
155+
y: "test",
156+
},
157+
},
158+
],
159+
};
160+
};
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import DropDownList from "components/common/DropDownList";
2+
import { getSegmentStatsAndTimeSeries } from "components/utilization/helper";
3+
import { FlexSection } from "components/utilization/styles";
4+
import { StatType } from "components/utilization/types";
5+
import { Segment, TimeSeriesWrapper } from "lib/utilization/types";
6+
import { cloneDeep } from "lodash";
7+
import { useEffect, useState } from "react";
8+
import { RankBar } from "./RankBar";
9+
10+
const statsNames = [
11+
{
12+
name: "avg",
13+
value: StatType.Average,
14+
},
15+
{
16+
name: "max",
17+
value: StatType.Max,
18+
},
19+
];
20+
21+
const DefaultResourceNames = [
22+
{
23+
name: "cpu",
24+
value: "cpu",
25+
},
26+
{
27+
name: "memory",
28+
value: "memory",
29+
},
30+
];
31+
32+
const DefaultGpuResourceValue = [
33+
{
34+
name: "all gpus utils",
35+
value: "gpus_util_all",
36+
},
37+
{
38+
name: "all gpu memory",
39+
value: "gpu_mem_all",
40+
},
41+
];
42+
43+
export const RankTestView = ({
44+
onRankClick = (id: string) => {},
45+
selectedId = "",
46+
timeSeriesList,
47+
segments,
48+
}: {
49+
onRankClick?: (id: string) => void;
50+
selectedId?: string | null;
51+
timeSeriesList: TimeSeriesWrapper[];
52+
segments: Segment[];
53+
}) => {
54+
const [rankData, setRankData] = useState<any[]>([]);
55+
const [selectResource, setSelectResource] = useState<string>("");
56+
const [selectStat, setSelectStat] = useState<string>("");
57+
const [resourceNames, setResourceNames] = useState<any[]>([]);
58+
59+
useEffect(() => {
60+
const rankData = processRankData(segments, timeSeriesList);
61+
let names = cloneDeep(DefaultResourceNames);
62+
63+
if (rankData.find((d) => d.resourceName.includes("gpu"))) {
64+
names = [...names, ...DefaultGpuResourceValue];
65+
}
66+
67+
setRankData(rankData);
68+
setResourceNames(names);
69+
70+
if (names.length === 0 || statsNames.length == 0) {
71+
return;
72+
}
73+
74+
setSelectResource(names[0].value);
75+
setSelectStat(statsNames[0].value);
76+
}, [segments, timeSeriesList]);
77+
78+
return (
79+
<div>
80+
<div>Rank Test View</div>
81+
<div>select resource and stats to pick the test you want to view</div>
82+
<FlexSection>
83+
<DropDownList
84+
onChange={function (value: string): void {
85+
setSelectResource(value);
86+
}}
87+
defaultValue={"cpu"}
88+
options={resourceNames}
89+
/>
90+
<DropDownList
91+
onChange={function (value: string): void {
92+
setSelectStat(value);
93+
}}
94+
defaultValue={StatType.Average}
95+
options={statsNames}
96+
/>
97+
</FlexSection>
98+
<div>
99+
<RankBar
100+
data={rankData}
101+
resourceName={selectResource}
102+
statType={selectStat}
103+
onRankClick={onRankClick}
104+
selectedId={selectedId}
105+
/>
106+
</div>
107+
</div>
108+
);
109+
};
110+
111+
function processRankData(
112+
segments: Segment[],
113+
timeSeriesList: TimeSeriesWrapper[]
114+
) {
115+
let allData: any[] = [];
116+
for (const segment of segments) {
117+
const data = getSegmentStatsAndTimeSeries(segment, timeSeriesList);
118+
if (!data) {
119+
console.log(
120+
`unable to get stats and time series data for ${segment.name}, something is wrong`
121+
);
122+
continue;
123+
}
124+
// flatten data for each test segment
125+
// {resourceName:"cpu", columns:[{type:"avg", value:0.1}, {type:"max", value:0.2}]} to {resourceName:"cpu", avg:0.1, max:0.2}
126+
const flattenedData = data.stats.map((s) => {
127+
const obj = s.columns.reduce(
128+
(acc, item) => ({ ...acc, [item.type]: item.value }),
129+
{}
130+
);
131+
return {
132+
name: segment.name,
133+
resourceName: s.name,
134+
...obj,
135+
};
136+
});
137+
allData = [...allData, ...flattenedData];
138+
}
139+
return allData;
140+
}

torchci/components/utilization/components/TestSectionView/SingleTestView.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import {
1010
InfoSection,
1111
InfoTitle,
1212
} from "components/utilization/styles";
13+
import { lineFilters } from "components/utilization/UtilizationPage";
1314
import { Segment, TimeSeriesWrapper } from "lib/utilization/types";
1415
import { useEffect, useState } from "react";
1516
import UtilizationJobMetricsTable from "../UtilizationStatsTable";
1617

1718
const StatsTable = styled("div")({
18-
width: "1200px",
19+
maxWidth: "1400px",
1920
margin: "10px",
2021
});
2122

@@ -26,6 +27,8 @@ const GraphGroupSection = styled("div")({
2627

2728
const SingleGraphSection = styled("div")({
2829
margin: "5px",
30+
padding: "10px",
31+
border: "1px solid #ccc",
2932
});
3033

3134
export const SingleTestView = ({
@@ -105,6 +108,7 @@ export const SingleTestView = ({
105108
chartWidth={1200}
106109
disableLineTooltip={false}
107110
disableRect={true}
111+
lineFilterConfig={lineFilters}
108112
></LineRectChart>
109113
)}
110114
</SingleGraphSection>
@@ -135,6 +139,5 @@ function getGithubSearchLink(testName: string) {
135139
const repo = `${testName}`;
136140
const encodedString = encodeURIComponent(repo);
137141
const url = head + `"` + encodedString + `"&type=code`;
138-
console.log(url);
139142
return url;
140143
}

0 commit comments

Comments
 (0)