Skip to content

Commit 3048f84

Browse files
authored
feat(TenantOverview): add cpu tab to tenant diagnostics (#550)
1 parent f31a19a commit 3048f84

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1433
-406
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.ydb-usage-label {
2+
&_overload {
3+
color: var(--yc-color-text-light-primary);
4+
background-color: var(--yc-color-base-danger-heavy);
5+
}
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import cn from 'bem-cn-lite';
2+
3+
import {Label, type LabelProps} from '@gravity-ui/uikit';
4+
5+
const b = cn('ydb-usage-label');
6+
7+
interface UsageLabelProps extends LabelProps {
8+
value: number | string;
9+
overloadThreshold?: number;
10+
}
11+
12+
export function UsageLabel({value, overloadThreshold = 90, theme, ...props}: UsageLabelProps) {
13+
return (
14+
<Label
15+
theme={theme}
16+
className={b({overload: Number(value) >= overloadThreshold})}
17+
{...props}
18+
>
19+
{value || 0}%
20+
</Label>
21+
);
22+
}

src/containers/Nodes/Nodes.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import {
2626
useNodesRequestParams,
2727
useTableSort,
2828
} from '../../utils/hooks';
29-
import {isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
29+
import {
30+
isSortableNodesProperty,
31+
isUnavailableNode,
32+
NodesUptimeFilterValues,
33+
} from '../../utils/nodes';
3034

3135
import {
3236
getNodes,
@@ -153,10 +157,14 @@ export const Nodes = ({path, type, additionalNodesProps = {}}: NodesProps) => {
153157
};
154158

155159
const renderTable = () => {
156-
const columns = getNodesColumns({
160+
const rawColumns = getNodesColumns({
157161
getNodeRef: additionalNodesProps.getNodeRef,
158162
});
159163

164+
const columns = rawColumns.map((column) => {
165+
return {...column, sortable: isSortableNodesProperty(column.name)};
166+
});
167+
160168
if (nodes && nodes.length === 0) {
161169
if (
162170
problemFilter !== ProblemFilterValues.ALL ||
Lines changed: 166 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import DataTable, {Column} from '@gravity-ui/react-data-table';
1+
import DataTable, {type Column} from '@gravity-ui/react-data-table';
22
import {Popover} from '@gravity-ui/uikit';
33

44
import {PoolsGraph} from '../../components/PoolsGraph/PoolsGraph';
55
import {ProgressViewer} from '../../components/ProgressViewer/ProgressViewer';
66
import {TabletsStatistic} from '../../components/TabletsStatistic';
77
import {NodeHostWrapper} from '../../components/NodeHostWrapper/NodeHostWrapper';
8-
9-
import {isSortableNodesProperty} from '../../utils/nodes';
108
import {formatBytesToGigabyte} from '../../utils/dataFormatters/dataFormatters';
11-
129
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
13-
14-
import type {NodeAddress} from '../../types/additionalProps';
10+
import type {GetNodeRefFunc} from '../../types/additionalProps';
11+
import {getLoadSeverityForNode} from '../../store/reducers/tenantOverview/topNodesByLoad/utils';
12+
import {UsageLabel} from '../../components/UsageLabel/UsageLabel';
1513

1614
const NODES_COLUMNS_IDS = {
1715
NodeId: 'NodeId',
@@ -24,131 +22,178 @@ const NODES_COLUMNS_IDS = {
2422
CPU: 'CPU',
2523
LoadAverage: 'LoadAverage',
2624
Tablets: 'Tablets',
25+
TopNodesLoadAverage: 'TopNodesLoadAverage',
2726
};
2827

2928
interface GetNodesColumnsProps {
3029
tabletsPath?: string;
31-
getNodeRef?: (node?: NodeAddress) => string | null;
30+
getNodeRef?: GetNodeRefFunc;
3231
}
3332

33+
const nodeIdColumn: Column<NodesPreparedEntity> = {
34+
name: NODES_COLUMNS_IDS.NodeId,
35+
header: '#',
36+
width: '80px',
37+
align: DataTable.RIGHT,
38+
sortable: false,
39+
};
40+
41+
const getHostColumn = (
42+
getNodeRef?: GetNodeRefFunc,
43+
fixedWidth = false,
44+
): Column<NodesPreparedEntity> => ({
45+
name: NODES_COLUMNS_IDS.Host,
46+
render: ({row}) => {
47+
return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
48+
},
49+
width: fixedWidth ? '350px' : undefined,
50+
align: DataTable.LEFT,
51+
sortable: false,
52+
});
53+
54+
const dataCenterColumn: Column<NodesPreparedEntity> = {
55+
name: NODES_COLUMNS_IDS.DC,
56+
header: 'DC',
57+
align: DataTable.LEFT,
58+
render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
59+
width: '60px',
60+
};
61+
62+
const rackColumn: Column<NodesPreparedEntity> = {
63+
name: NODES_COLUMNS_IDS.Rack,
64+
header: 'Rack',
65+
align: DataTable.LEFT,
66+
render: ({row}) => (row.Rack ? row.Rack : '—'),
67+
width: '80px',
68+
};
69+
70+
const versionColumn: Column<NodesPreparedEntity> = {
71+
name: NODES_COLUMNS_IDS.Version,
72+
width: '200px',
73+
align: DataTable.LEFT,
74+
render: ({row}) => {
75+
return <Popover content={row.Version}>{row.Version}</Popover>;
76+
},
77+
sortable: false,
78+
};
79+
80+
const uptimeColumn: Column<NodesPreparedEntity> = {
81+
name: NODES_COLUMNS_IDS.Uptime,
82+
header: 'Uptime',
83+
sortAccessor: ({StartTime}) => StartTime && -StartTime,
84+
align: DataTable.RIGHT,
85+
width: '110px',
86+
};
87+
88+
const memoryColumn: Column<NodesPreparedEntity> = {
89+
name: NODES_COLUMNS_IDS.Memory,
90+
header: 'Memory',
91+
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
92+
defaultOrder: DataTable.DESCENDING,
93+
render: ({row}) => {
94+
if (row.MemoryUsed) {
95+
return formatBytesToGigabyte(row.MemoryUsed);
96+
} else {
97+
return '—';
98+
}
99+
},
100+
align: DataTable.RIGHT,
101+
width: '120px',
102+
};
103+
104+
const cpuColumn: Column<NodesPreparedEntity> = {
105+
name: NODES_COLUMNS_IDS.CPU,
106+
header: 'CPU',
107+
sortAccessor: ({PoolStats = []}) => Math.max(...PoolStats.map(({Usage}) => Number(Usage))),
108+
defaultOrder: DataTable.DESCENDING,
109+
render: ({row}) => (row.PoolStats ? <PoolsGraph pools={row.PoolStats} /> : '—'),
110+
align: DataTable.LEFT,
111+
width: '80px',
112+
sortable: false,
113+
};
114+
115+
const loadAverageColumn: Column<NodesPreparedEntity> = {
116+
name: NODES_COLUMNS_IDS.LoadAverage,
117+
header: 'Load average',
118+
sortAccessor: ({LoadAverage = []}) =>
119+
LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
120+
defaultOrder: DataTable.DESCENDING,
121+
render: ({row}) =>
122+
row.LoadAverage && row.LoadAverage.length > 0 ? (
123+
<ProgressViewer
124+
value={row.LoadAverage[0]}
125+
percents={true}
126+
colorizeProgress={true}
127+
capacity={100}
128+
/>
129+
) : (
130+
'—'
131+
),
132+
align: DataTable.LEFT,
133+
width: '140px',
134+
sortable: false,
135+
};
136+
137+
const getTabletsColumn = (tabletsPath?: string): Column<NodesPreparedEntity> => ({
138+
name: NODES_COLUMNS_IDS.Tablets,
139+
width: '430px',
140+
render: ({row}) => {
141+
return row.Tablets ? (
142+
<TabletsStatistic
143+
path={tabletsPath ?? row.TenantName}
144+
nodeIds={[row.NodeId]}
145+
tablets={row.Tablets}
146+
/>
147+
) : (
148+
'—'
149+
);
150+
},
151+
align: DataTable.LEFT,
152+
});
153+
154+
const topNodesLoadAverageColumn: Column<NodesPreparedEntity> = {
155+
name: NODES_COLUMNS_IDS.TopNodesLoadAverage,
156+
header: 'Load',
157+
render: ({row}) =>
158+
row.LoadAverage && row.LoadAverage.length > 0 ? (
159+
<UsageLabel
160+
value={row.LoadAverage[0].toFixed()}
161+
theme={getLoadSeverityForNode(row.LoadAverage[0])}
162+
/>
163+
) : (
164+
'—'
165+
),
166+
align: DataTable.LEFT,
167+
width: '80px',
168+
sortable: false,
169+
};
170+
34171
export function getNodesColumns({
35172
tabletsPath,
36173
getNodeRef,
37174
}: GetNodesColumnsProps): Column<NodesPreparedEntity>[] {
38-
const columns: Column<NodesPreparedEntity>[] = [
39-
{
40-
name: NODES_COLUMNS_IDS.NodeId,
41-
header: '#',
42-
width: '80px',
43-
align: DataTable.RIGHT,
44-
},
45-
{
46-
name: NODES_COLUMNS_IDS.Host,
47-
render: ({row}) => {
48-
return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
49-
},
50-
width: '350px',
51-
align: DataTable.LEFT,
52-
},
53-
{
54-
name: NODES_COLUMNS_IDS.DC,
55-
header: 'DC',
56-
align: DataTable.LEFT,
57-
render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
58-
width: '60px',
59-
},
60-
{
61-
name: NODES_COLUMNS_IDS.Rack,
62-
header: 'Rack',
63-
align: DataTable.LEFT,
64-
render: ({row}) => (row.Rack ? row.Rack : '—'),
65-
width: '80px',
66-
},
67-
{
68-
name: NODES_COLUMNS_IDS.Version,
69-
width: '200px',
70-
align: DataTable.LEFT,
71-
render: ({row}) => {
72-
return <Popover content={row.Version}>{row.Version}</Popover>;
73-
},
74-
},
75-
{
76-
name: NODES_COLUMNS_IDS.Uptime,
77-
header: 'Uptime',
78-
sortAccessor: ({StartTime}) => StartTime && -StartTime,
79-
align: DataTable.RIGHT,
80-
width: '110px',
81-
},
82-
{
83-
name: NODES_COLUMNS_IDS.Memory,
84-
header: 'Memory',
85-
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
86-
defaultOrder: DataTable.DESCENDING,
87-
render: ({row}) => {
88-
if (row.MemoryUsed) {
89-
return formatBytesToGigabyte(row.MemoryUsed);
90-
} else {
91-
return '—';
92-
}
93-
},
94-
align: DataTable.RIGHT,
95-
width: '120px',
96-
},
97-
{
98-
name: NODES_COLUMNS_IDS.CPU,
99-
header: 'CPU',
100-
sortAccessor: ({PoolStats = []}) =>
101-
PoolStats.reduce((acc, item) => {
102-
if (item.Usage) {
103-
return acc + item.Usage;
104-
} else {
105-
return acc;
106-
}
107-
}, 0),
108-
defaultOrder: DataTable.DESCENDING,
109-
render: ({row}) => (row.PoolStats ? <PoolsGraph pools={row.PoolStats} /> : '—'),
110-
align: DataTable.LEFT,
111-
width: '120px',
112-
},
113-
{
114-
name: NODES_COLUMNS_IDS.LoadAverage,
115-
header: 'Load average',
116-
sortAccessor: ({LoadAverage = []}) =>
117-
LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
118-
defaultOrder: DataTable.DESCENDING,
119-
render: ({row}) =>
120-
row.LoadAverage && row.LoadAverage.length > 0 ? (
121-
<ProgressViewer
122-
value={row.LoadAverage[0]}
123-
capacity={100}
124-
percents={true}
125-
colorizeProgress={true}
126-
/>
127-
) : (
128-
'—'
129-
),
130-
align: DataTable.LEFT,
131-
width: '140px',
132-
},
133-
{
134-
name: NODES_COLUMNS_IDS.Tablets,
135-
width: '430px',
136-
render: ({row}) => {
137-
return row.Tablets ? (
138-
<TabletsStatistic
139-
path={tabletsPath ?? row.TenantName}
140-
nodeIds={[row.NodeId]}
141-
tablets={row.Tablets}
142-
/>
143-
) : (
144-
'—'
145-
);
146-
},
147-
align: DataTable.LEFT,
148-
},
175+
return [
176+
nodeIdColumn,
177+
getHostColumn(getNodeRef, true),
178+
dataCenterColumn,
179+
rackColumn,
180+
versionColumn,
181+
uptimeColumn,
182+
memoryColumn,
183+
cpuColumn,
184+
loadAverageColumn,
185+
getTabletsColumn(tabletsPath),
149186
];
187+
}
188+
189+
export function getTopNodesByLoadColumns(
190+
getNodeRef?: GetNodeRefFunc,
191+
): Column<NodesPreparedEntity>[] {
192+
return [topNodesLoadAverageColumn, nodeIdColumn, getHostColumn(getNodeRef), versionColumn];
193+
}
150194

151-
return columns.map((column) => {
152-
return {...column, sortable: isSortableNodesProperty(column.name)};
153-
});
195+
export function getTopNodesByCpuColumns(
196+
getNodeRef?: GetNodeRefFunc,
197+
): Column<NodesPreparedEntity>[] {
198+
return [cpuColumn, nodeIdColumn, getHostColumn(getNodeRef)];
154199
}

0 commit comments

Comments
 (0)