Skip to content

Commit e95ba0f

Browse files
author
Lingxi Chen
committed
updated boxplot
Signed-off-by: Lingxi Chen <[email protected]>
1 parent 992f2f8 commit e95ba0f

File tree

3 files changed

+109
-66
lines changed

3 files changed

+109
-66
lines changed

public/pages/WorkloadManagement/WLMDetails/WLMDetails.tsx

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ interface NodeUsageData {
6464

6565
interface WorkloadGroupDetails {
6666
name: string;
67-
cpuLimit: number | string;
68-
memLimit: number | string;
67+
cpuLimit: number | undefined;
68+
memLimit: number | undefined;
6969
resiliencyMode: 'soft' | 'enforced';
7070
description: string;
7171
}
@@ -138,8 +138,8 @@ export const WLMDetails = ({
138138
// === State ===
139139
const [groupDetails, setGroupDetails] = useState<WorkloadGroupDetails | null>(null);
140140
const [resiliencyMode, setResiliencyMode] = useState<ResiliencyMode>(ResiliencyMode.SOFT);
141-
const [cpuLimit, setCpuLimit] = useState(DEFAULT_RESOURCE_LIMIT);
142-
const [memoryLimit, setMemoryLimit] = useState(DEFAULT_RESOURCE_LIMIT);
141+
const [cpuLimit, setCpuLimit] = useState<number | undefined>();
142+
const [memoryLimit, setMemoryLimit] = useState<number | undefined>();
143143
const [isSaved, setIsSaved] = useState(true);
144144
const [nodesData, setNodesData] = useState<NodeUsageData[]>([]);
145145
const [sortedData, setSortedData] = useState<NodeUsageData[]>([]);
@@ -230,18 +230,14 @@ export const WLMDetails = ({
230230
if (workload) {
231231
setGroupDetails({
232232
name: workload.name,
233-
cpuLimit:
234-
workload.resource_limits?.cpu != null ? formatLimit(workload.resource_limits.cpu) : '-',
235-
memLimit:
236-
workload.resource_limits?.memory != null
237-
? formatLimit(workload.resource_limits.memory)
238-
: '-',
233+
cpuLimit: formatLimit(workload.resource_limits?.cpu),
234+
memLimit: formatLimit(workload.resource_limits?.memory),
239235
resiliencyMode: workload.resiliency_mode,
240236
description: '-',
241237
});
242238
setResiliencyMode(workload.resiliency_mode.toLowerCase());
243-
setCpuLimit(formatLimit(workload.resource_limits.cpu));
244-
setMemoryLimit(formatLimit(workload.resource_limits.memory));
239+
setCpuLimit(formatLimit(workload.resource_limits?.cpu));
240+
setMemoryLimit(formatLimit(workload.resource_limits?.memory));
245241
}
246242
} catch (err) {
247243
console.error('Failed to fetch workload group details:', err);
@@ -291,11 +287,11 @@ export const WLMDetails = ({
291287

292288
const statsForGroup = data.workload_groups[groupId];
293289

294-
if (statsForGroup) {
290+
if (statsForGroup && statsForGroup.cpu && statsForGroup.memory) {
295291
nodeStatsList.push({
296292
nodeId,
297-
cpuUsage: formatLimit(statsForGroup.cpu?.current_usage),
298-
memoryUsage: formatLimit(statsForGroup.memory?.current_usage),
293+
cpuUsage: formatUsage(statsForGroup.cpu.current_usage),
294+
memoryUsage: formatUsage(statsForGroup.memory.current_usage),
299295
});
300296
}
301297
}
@@ -309,23 +305,32 @@ export const WLMDetails = ({
309305

310306
// === Actions ===
311307
const saveChanges = async () => {
312-
if (cpuLimit <= 0 || cpuLimit > 100 || memoryLimit <= 0 || memoryLimit > 100) {
308+
const validCpu = cpuLimit === undefined || (cpuLimit > 0 && cpuLimit <= 100);
309+
const validMem = memoryLimit === undefined || (memoryLimit > 0 && memoryLimit <= 100);
310+
311+
if (!resiliencyMode || (!validCpu && !validMem)) {
313312
core.notifications.toasts.addDanger(
314-
'CPU and Memory limits must be between 0 and 100 (exclusive of 0)'
313+
'Resiliency mode is required and at least one of CPU or memory limits must be valid (1–100).'
315314
);
316315
return;
317316
}
318317

318+
const resourceLimits: Record<string, number> = {};
319+
if (cpuLimit !== undefined) resourceLimits.cpu = cpuLimit / 100;
320+
if (memoryLimit !== undefined) resourceLimits.memory = memoryLimit / 100;
321+
322+
const body: Record<string, any> = {
323+
resiliency_mode: resiliencyMode.toUpperCase(),
324+
};
325+
326+
if (Object.keys(resourceLimits).length > 0) {
327+
body.resource_limits = resourceLimits;
328+
}
329+
319330
try {
320331
await core.http.put(`/api/_wlm/workload_group/${groupName}`, {
321332
query: { dataSourceId: dataSource.id },
322-
body: JSON.stringify({
323-
resiliency_mode: resiliencyMode,
324-
resource_limits: {
325-
cpu: cpuLimit / 100,
326-
memory: memoryLimit / 100,
327-
},
328-
}),
333+
body: JSON.stringify(body),
329334
});
330335

331336
setIsSaved(true);
@@ -378,10 +383,16 @@ export const WLMDetails = ({
378383
}
379384
};
380385

381-
const formatLimit = (usage?: number, defaultValue: number = 0): number => {
382-
return Math.round((usage ?? defaultValue) * 100);
386+
const formatLimit = (usage?: number): number | undefined => {
387+
if (usage == null) return undefined;
388+
return Math.round(usage * 100);
389+
};
390+
391+
const formatUsage = (usage: number): number => {
392+
return Math.round(usage * 100);
383393
};
384394

395+
385396
return (
386397
<div style={{ padding: '20px 40px' }}>
387398
{isDeleteModalVisible && (
@@ -633,13 +644,14 @@ export const WLMDetails = ({
633644
{/* CPU Usage Limit */}
634645
<EuiFormRow
635646
label="Reject queries when CPU usage exceeds"
636-
isInvalid={cpuLimit <= 0 || cpuLimit > 100}
647+
isInvalid={cpuLimit !== undefined && (cpuLimit <= 0 || cpuLimit > 100)}
637648
error="Value must be between 0 and 100"
638649
>
639650
<EuiFieldNumber
640651
value={cpuLimit}
641652
onChange={(e) => {
642-
setCpuLimit(Number(e.target.value));
653+
const val = e.target.value;
654+
setCpuLimit(val === '' ? undefined : Number(val));
643655
setIsSaved(false);
644656
}}
645657
append="%"
@@ -653,13 +665,14 @@ export const WLMDetails = ({
653665
{/* Memory Usage Limit */}
654666
<EuiFormRow
655667
label="Reject queries when memory usage exceeds"
656-
isInvalid={memoryLimit <= 0 || memoryLimit > 100}
668+
isInvalid={memoryLimit !== undefined && (memoryLimit <= 0 || memoryLimit > 100)}
657669
error="Value must be between 0 and 100"
658670
>
659671
<EuiFieldNumber
660672
value={memoryLimit}
661673
onChange={(e) => {
662-
setMemoryLimit(Number(e.target.value));
674+
const val = e.target.value;
675+
setMemoryLimit(val === '' ? undefined : Number(val));
663676
setIsSaved(false);
664677
}}
665678
append="%"

public/pages/WorkloadManagement/WLMMain/WLMMain.tsx

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -376,52 +376,82 @@ export const WorkloadManagementMain = ({
376376

377377
const getBoxplotOption = (box: number[], limit: number) => {
378378
const sorted = [...box].sort((a, b) => a - b);
379-
const [boxMin, boxQ1, boxMedian, boxQ3, boxMax] = sorted;
379+
let [boxMin, boxQ1, boxMedian, boxQ3, boxMax] = sorted;
380+
const AXIS_MIN = 0;
381+
const AXIS_MAX = 100;
382+
380383
return {
381384
tooltip: {
382-
trigger: 'item',
383-
appendToBody: true,
385+
trigger: 'axis',
384386
className: 'echarts-tooltip',
385-
formatter: (localParams: any) => {
386-
if (localParams.seriesType !== 'boxplot') return '';
387-
const [fMin, fQ1, fMedian, fQ3, fMax] = localParams.data
388-
.slice(1, 6)
389-
.map((v: number) => v.toFixed(2));
390-
const formattedLimit = Number(limit).toFixed(2);
391-
return `<strong>Usage across nodes</strong><br/>
392-
Min: ${fMin}%<br/>
393-
Q1: ${fQ1}%<br/>
394-
Median: ${fMedian}%<br/>
395-
Q3: ${fQ3}%<br/>
396-
Max: ${fMax}%<br/>
397-
<span style="color:#dc3545;">Limit: ${formattedLimit}%</span>`;
398-
},
387+
formatter: (params: any[]) => {
388+
const box = params.find(p => p.seriesType === 'boxplot');
389+
390+
let tooltip = '';
391+
if (box) {
392+
const [fMin, fQ1, fMedian, fQ3, fMax] = box.data.slice(1, 6).map((v: number) => v.toFixed(2));
393+
tooltip += `<strong>Usage across nodes (boxplot)</strong><br/>
394+
Min: ${fMin}%<br/>
395+
Q1: ${fQ1}%<br/>
396+
Median: ${fMedian}%<br/>
397+
Q3: ${fQ3}%<br/>
398+
Max: ${fMax}%<br/>`;
399+
}
400+
401+
tooltip += `<span style="color:#dc3545;">Limit: ${limit.toFixed(2)}%</span>`;
402+
403+
return tooltip;
404+
}
399405
},
400-
grid: { left: '0%', right: '10%', top: '0%', bottom: '0%' },
406+
animation: false,
407+
grid: { left: '5%', right: '5%', top: '-1%', bottom: '-1%' },
401408
xAxis: {
402409
type: 'value',
403-
min: Math.min(boxMin, limit) - 5,
404-
max: Math.max(boxMax, limit) + 5,
405-
show: false,
410+
min: AXIS_MIN,
411+
max: AXIS_MAX,
412+
axisLine: { show: false },
413+
axisTick: { show: false },
414+
axisLabel: { show: false },
415+
splitLine: {
416+
show: true,
417+
lineStyle: {
418+
color: ['#000000', '#e0e0e0', '#e0e0e0', '#e0e0e0', '#e0e0e0', '#000000'],
419+
type: 'solid',
420+
width: 1,
421+
},
422+
},
423+
},
424+
yAxis: {
425+
type: 'category',
426+
data: ['Boxplot'],
427+
axisLabel: { show: false },
406428
},
407-
yAxis: { type: 'category', data: ['Boxplot'], show: false },
408429
series: [
409430
{
410-
name: 'Boxplot',
431+
name: 'Usage Distribution',
411432
type: 'boxplot',
412433
data: [[boxMin, boxQ1, boxMedian, boxQ3, boxMax]],
413-
itemStyle: { color: '#0268BC', borderColor: 'black' },
434+
itemStyle: { color: '#79AAD9', borderColor: '#000', borderWidth: 1.25 },
414435
boxWidth: ['40%', '50%'],
436+
437+
markLine: {
438+
symbol: 'none',
439+
label: {
440+
formatter: '#DC3545',
441+
position: 'end',
442+
color: "danger",
443+
},
444+
lineStyle: {
445+
color: '#DC3545',
446+
type: 'solid',
447+
width: 2,
448+
},
449+
data: [
450+
{ xAxis: limit }
451+
],
452+
},
415453
},
416-
{
417-
name: 'Limit',
418-
type: 'scatter',
419-
symbol: 'rect',
420-
symbolSize: [3, 35],
421-
data: [[limit, 0]],
422-
itemStyle: { color: '#dc3545' },
423-
},
424-
],
454+
]
425455
};
426456
};
427457

@@ -567,7 +597,7 @@ export const WorkloadManagementMain = ({
567597
<EuiFlexItem grow={false}>
568598
<EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}>
569599
<EuiFlexItem grow={false}>
570-
<EuiFormRow label="Data source" display="columnCompressed">
600+
<EuiFormRow label="Node selection" display="columnCompressed">
571601
<EuiSelect
572602
options={nodeIds.map((id) => ({ value: id, text: id }))}
573603
value={selectedNode || ''}

server/routes/wlmRoutes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ export function defineWlmRoutes(router: IRouter) {
159159
body: schema.object({
160160
resiliency_mode: schema.string(),
161161
resource_limits: schema.object({
162-
cpu: schema.number(),
163-
memory: schema.number(),
162+
cpu: schema.maybe(schema.number()),
163+
memory: schema.maybe(schema.number()),
164164
}),
165165
}),
166166
},

0 commit comments

Comments
 (0)