Skip to content

Commit 65dcb3d

Browse files
committed
fix: use effective YDB memory limit for node RAM
1 parent 02ac0d0 commit 65dcb3d

11 files changed

Lines changed: 320 additions & 36 deletions

File tree

‎src/components/FullNodeViewer/FullNodeViewer.tsx‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {cn} from '../../utils/cn';
55
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
66
import {formatStorageValues} from '../../utils/dataFormatters/dataFormatters';
77
import {useNodeDeveloperUIHref} from '../../utils/hooks/useNodeDeveloperUIHref';
8-
import {isNumeric} from '../../utils/utils';
8+
import {getNodeMemory} from '../../utils/memory';
99
import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';
1010
import {PoolUsage} from '../PoolUsage/PoolUsage';
1111
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
@@ -90,18 +90,13 @@ export const FullNodeViewer = ({node, className}: FullNodeViewerProps) => {
9090
return <div className="error">{i18n('no-data')}</div>;
9191
}
9292

93-
const memoryUsed = isNumeric(node.MemoryUsed) ? Number(node.MemoryUsed) : undefined;
94-
const memoryLimit = isNumeric(node.MemoryLimit) ? Number(node.MemoryLimit) : undefined;
95-
const hasMemoryUsed =
96-
memoryUsed !== undefined && Number.isFinite(memoryUsed) && memoryUsed >= 0;
97-
const hasMemoryLimit =
98-
memoryLimit !== undefined && Number.isFinite(memoryLimit) && memoryLimit > 0;
93+
const {memoryUsed, memoryLimit} = getNodeMemory(node);
9994

10095
const renderMemory = () => {
101-
if (!hasMemoryUsed) {
96+
if (memoryUsed === undefined) {
10297
return EMPTY_DATA_PLACEHOLDER;
10398
}
104-
if (!hasMemoryLimit) {
99+
if (memoryLimit === undefined) {
105100
return formatMemoryValues(memoryUsed)[0];
106101
}
107102

‎src/components/MemoryViewer/MemoryViewer.tsx‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import {DefinitionList, Flex, useTheme} from '@gravity-ui/uikit';
33
import type {TMemoryStats} from '../../types/api/nodes';
44
import {formatBytes} from '../../utils/bytesParsers';
55
import {cn} from '../../utils/cn';
6-
import {GIGABYTE} from '../../utils/constants';
6+
import {EMPTY_DATA_PLACEHOLDER, GIGABYTE} from '../../utils/constants';
7+
import {getNodeMemory} from '../../utils/memory';
78
import type {FormatProgressViewerValues} from '../../utils/progress';
89
import {calculateProgressStatus} from '../../utils/progress';
910
import {isNumeric} from '../../utils/utils';
1011
import {HoverPopup} from '../HoverPopup/HoverPopup';
1112
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
1213

13-
import {calculateAllocatedMemory, getMemorySegments} from './utils';
14+
import {getMemorySegments} from './utils';
1415

1516
import './MemoryViewer.scss';
1617

@@ -38,6 +39,8 @@ const formatDetailedValues: FormatProgressViewerValues = (value, total) => {
3839

3940
export interface MemoryProgressViewerProps {
4041
stats: TMemoryStats;
42+
memoryUsed?: string;
43+
memoryLimit?: string;
4144
className?: string;
4245
warningThreshold?: number;
4346
dangerThreshold?: number;
@@ -47,17 +50,28 @@ export interface MemoryProgressViewerProps {
4750

4851
export function MemoryViewer({
4952
stats,
53+
memoryUsed: fallbackMemoryUsed,
54+
memoryLimit: fallbackMemoryLimit,
5055
percents,
5156
formatValues,
5257
className,
5358
warningThreshold,
5459
dangerThreshold,
5560
}: MemoryProgressViewerProps) {
56-
const memoryUsage = stats.AnonRss ?? calculateAllocatedMemory(stats);
61+
const {memoryUsed: memoryUsage, memoryLimit: capacity} = getNodeMemory({
62+
MemoryStats: stats,
63+
MemoryUsed: fallbackMemoryUsed,
64+
MemoryLimit: fallbackMemoryLimit,
65+
});
66+
const theme = useTheme();
5767

58-
const capacity = stats.HardLimit;
68+
if (memoryUsage === undefined) {
69+
return EMPTY_DATA_PLACEHOLDER;
70+
}
71+
if (capacity === undefined) {
72+
return formatValues(memoryUsage)[0];
73+
}
5974

60-
const theme = useTheme();
6175
let fillWidth =
6276
Math.floor((parseFloat(String(memoryUsage)) / parseFloat(String(capacity))) * 100) || 0;
6377
fillWidth = fillWidth > 100 ? 100 : fillWidth;

‎src/components/MemoryViewer/utils.ts‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ import {isNumeric} from '../../utils/utils';
33

44
import i18n from './i18n';
55

6-
export function calculateAllocatedMemory(stats: TMemoryStats) {
7-
const allocatedMemory = getMaybeNumber(stats.AllocatedMemory) || 0;
8-
const allocatorCaches = getMaybeNumber(stats.AllocatorCachesMemory) || 0;
9-
return String(allocatedMemory + allocatorCaches);
10-
}
11-
126
function getMaybeNumber(value: string | number | undefined): number | undefined {
137
return isNumeric(value) ? parseFloat(String(value)) : undefined;
148
}

‎src/components/nodesColumns/__test__/constants.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ describe('storage nodes capacity column contracts', () => {
2424
expect(getNodesColumnSortField(columnId)).toBe(expectedSortField);
2525
});
2626
});
27+
28+
test('requests detailed memory when only RAM is selected', () => {
29+
expect(getRequiredDataFields([NODES_COLUMNS_IDS.RAM], NODES_COLUMNS_TO_DATA_FIELDS)).toEqual([
30+
'Memory',
31+
'MemoryDetailed',
32+
]);
33+
});

‎src/components/nodesColumns/columns.tsx‎

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
formatStorageValuesToGb,
1414
} from '../../utils/dataFormatters/dataFormatters';
1515
import {getUsageSeverity} from '../../utils/generateEvaluator';
16+
import {getNodeMemory} from '../../utils/memory';
1617
import type {Column} from '../../utils/tableUtils/types';
1718
import {formatToMs, parseUsToMs} from '../../utils/timeParsers';
1819
import {bytesToSpeed, isNumeric} from '../../utils/utils';
@@ -169,22 +170,26 @@ export function getUptimeColumn<
169170
};
170171
}
171172

172-
export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: string}>(): Column<T> {
173+
export function getRAMColumn<
174+
T extends {MemoryStats?: TMemoryStats; MemoryUsed?: string; MemoryLimit?: string},
175+
>(): Column<T> {
173176
return {
174177
name: NODES_COLUMNS_IDS.RAM,
175178
header: NODES_COLUMNS_TITLES.RAM,
176-
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
179+
sortAccessor: (row) => getNodeMemory(row).memoryUsed ?? 0,
177180
defaultOrder: DataTable.DESCENDING,
178181
render: ({row}) => {
182+
const {memoryUsed: used, memoryLimit: limit} = getNodeMemory(row);
179183
const [memoryUsed, memoryLimit] = formatStorageValues(
180-
isNumeric(row.MemoryUsed) ? Number(row.MemoryUsed) : undefined,
181-
isNumeric(row.MemoryLimit) ? Number(row.MemoryLimit) : undefined,
184+
used,
185+
limit,
182186
'gb',
183187
undefined,
184188
true,
185189
);
186190

187191
const hasData = memoryUsed || memoryLimit;
192+
const memoryContent = used === undefined ? EMPTY_DATA_PLACEHOLDER : memoryUsed;
188193

189194
return (
190195
<CellWithPopover
@@ -206,10 +211,10 @@ export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: strin
206211
</DefinitionList>
207212
}
208213
>
209-
{isNumeric(row.MemoryUsed) ? (
214+
{used !== undefined && limit !== undefined ? (
210215
<ProgressViewer
211-
value={row.MemoryUsed}
212-
capacity={row.MemoryLimit}
216+
value={used}
217+
capacity={limit}
213218
formatValues={(value, total) =>
214219
formatStorageValues(value, total, 'gb', undefined, true)
215220
}
@@ -218,7 +223,7 @@ export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: strin
218223
hideCapacity
219224
/>
220225
) : (
221-
EMPTY_DATA_PLACEHOLDER
226+
memoryContent
222227
)}
223228
</CellWithPopover>
224229
);
@@ -238,18 +243,27 @@ export function getMemoryColumn<
238243
render: ({row}) => {
239244
if (row.MemoryStats) {
240245
return (
241-
<MemoryViewer formatValues={formatStorageValuesToGb} stats={row.MemoryStats} />
246+
<MemoryViewer
247+
formatValues={formatStorageValuesToGb}
248+
stats={row.MemoryStats}
249+
memoryUsed={row.MemoryUsed}
250+
memoryLimit={row.MemoryLimit}
251+
/>
242252
);
243253
}
244254

245-
if (!isNumeric(row.MemoryUsed)) {
255+
const {memoryUsed, memoryLimit} = getNodeMemory(row);
256+
if (memoryUsed === undefined) {
246257
return EMPTY_DATA_PLACEHOLDER;
247258
}
259+
if (memoryLimit === undefined) {
260+
return formatStorageValuesToGb(memoryUsed)[0];
261+
}
248262

249263
return (
250264
<ProgressViewer
251-
value={row.MemoryUsed}
252-
capacity={row.MemoryLimit}
265+
value={memoryUsed}
266+
capacity={memoryLimit}
253267
formatValues={formatStorageValuesToGb}
254268
colorizeProgress={true}
255269
/>

‎src/components/nodesColumns/constants.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record<NodesColumnId, NodesRequiredFi
232232
Version: ['Version'],
233233
Uptime: ['Uptime', 'DisconnectTime'],
234234
Memory: ['Memory', 'MemoryDetailed'],
235-
RAM: ['Memory'],
235+
RAM: ['Memory', 'MemoryDetailed'],
236236
Pools: ['CPU'],
237237
CPU: ['CPU'],
238238
LoadAverage: ['LoadAverage'],

‎src/containers/Versions/Versions.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ interface VersionsContainerProps {
3333
export function VersionsContainer({cluster, loading}: VersionsContainerProps) {
3434
const [autoRefreshInterval] = useAutoRefreshInterval();
3535
const {currentData, isLoading: isNodesLoading} = nodesApi.useGetNodesQuery(
36-
{tablets: false, fieldsRequired: ['SystemState', 'SubDomainKey']},
36+
{tablets: false, fieldsRequired: ['SystemState', 'SubDomainKey', 'MemoryDetailed']},
3737
{pollingInterval: autoRefreshInterval},
3838
);
3939
const versionsDataMap = useVersionsDataMap(cluster);

‎src/utils/__test__/memory.test.ts‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {getNodeMemory} from '../memory';
2+
3+
describe('getNodeMemory', () => {
4+
test('uses the process consumption and effective YDB limit instead of host memory', () => {
5+
expect(
6+
getNodeMemory({
7+
MemoryUsed: '21474836480',
8+
MemoryLimit: '34359738368',
9+
MemoryStats: {AnonRss: '21474836480', HardLimit: '25769803776'},
10+
}),
11+
).toEqual({memoryUsed: 21474836480, memoryLimit: 25769803776});
12+
});
13+
14+
test('prefers anonymous RSS over allocator and legacy usage, including zero', () => {
15+
expect(
16+
getNodeMemory({
17+
MemoryUsed: '200',
18+
MemoryStats: {AnonRss: '0', AllocatedMemory: '100', AllocatorCachesMemory: '20'},
19+
}),
20+
).toEqual({memoryUsed: 0, memoryLimit: undefined});
21+
});
22+
23+
test('includes allocator caches when anonymous RSS is unavailable', () => {
24+
expect(
25+
getNodeMemory({
26+
MemoryUsed: '100',
27+
MemoryStats: {AllocatedMemory: '100', AllocatorCachesMemory: '20'},
28+
}),
29+
).toEqual({memoryUsed: 120, memoryLimit: undefined});
30+
});
31+
32+
test.each([{AllocatedMemory: '100'}, {AllocatorCachesMemory: '100'}])(
33+
'uses the available allocator counters: %j',
34+
(MemoryStats) => {
35+
expect(getNodeMemory({MemoryStats}).memoryUsed).toBe(100);
36+
},
37+
);
38+
39+
test.each([undefined, {}, {HardLimit: '250'}])(
40+
'preserves legacy usage when detailed consumption is absent: %j',
41+
(MemoryStats) => {
42+
expect(getNodeMemory({MemoryUsed: '100', MemoryLimit: '250', MemoryStats})).toEqual({
43+
memoryUsed: 100,
44+
memoryLimit: 250,
45+
});
46+
},
47+
);
48+
49+
test.each(['', ' ', 'invalid', '-1', 'Infinity'])('ignores invalid stats: %j', (value) => {
50+
expect(
51+
getNodeMemory({
52+
MemoryUsed: '100',
53+
MemoryLimit: '250',
54+
MemoryStats: {
55+
AnonRss: value,
56+
AllocatedMemory: value,
57+
AllocatorCachesMemory: value,
58+
HardLimit: value,
59+
},
60+
}),
61+
).toEqual({memoryUsed: 100, memoryLimit: 250});
62+
});
63+
64+
test('falls back to the legacy limit for a zero hard limit', () => {
65+
expect(getNodeMemory({MemoryLimit: '250', MemoryStats: {HardLimit: '0'}}).memoryLimit).toBe(
66+
250,
67+
);
68+
});
69+
70+
test.each([undefined, '', 'invalid', '-1', 'Infinity'])(
71+
'keeps unknown usage absent: %j',
72+
(value) => {
73+
expect(getNodeMemory({MemoryUsed: value, MemoryStats: {}}).memoryUsed).toBeUndefined();
74+
},
75+
);
76+
77+
test.each([undefined, '', 'invalid', '0', '-1', 'Infinity'])(
78+
'keeps unknown limits absent: %j',
79+
(value) => {
80+
expect(
81+
getNodeMemory({MemoryLimit: value, MemoryStats: {HardLimit: value}}).memoryLimit,
82+
).toBeUndefined();
83+
},
84+
);
85+
});

‎src/utils/memory.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type {TSystemStateInfo} from '../types/api/nodes';
2+
3+
import {parseOptionalNonNegativeNumber} from './utils';
4+
5+
export function getNodeMemory(
6+
node: Pick<TSystemStateInfo, 'MemoryStats' | 'MemoryUsed' | 'MemoryLimit'>,
7+
) {
8+
const stats = node.MemoryStats;
9+
const anonRss = parseOptionalNonNegativeNumber(stats?.AnonRss);
10+
const allocated = parseOptionalNonNegativeNumber(stats?.AllocatedMemory);
11+
const caches = parseOptionalNonNegativeNumber(stats?.AllocatorCachesMemory);
12+
const allocatorUsage =
13+
allocated !== undefined || caches !== undefined
14+
? (allocated ?? 0) + (caches ?? 0)
15+
: undefined;
16+
17+
const memoryUsed = anonRss ?? allocatorUsage ?? parseOptionalNonNegativeNumber(node.MemoryUsed);
18+
const memoryLimit =
19+
parseOptionalNonNegativeNumber(stats?.HardLimit) ||
20+
parseOptionalNonNegativeNumber(node.MemoryLimit) ||
21+
undefined;
22+
23+
return {memoryUsed, memoryLimit};
24+
}

0 commit comments

Comments
 (0)