Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions src/components/FullNodeViewer/FullNodeViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {cn} from '../../utils/cn';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
import {formatStorageValues} from '../../utils/dataFormatters/dataFormatters';
import {useNodeDeveloperUIHref} from '../../utils/hooks/useNodeDeveloperUIHref';
import {isNumeric} from '../../utils/utils';
import {getNodeMemory} from '../../utils/memory';
import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon';
import {PoolUsage} from '../PoolUsage/PoolUsage';
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
Expand Down Expand Up @@ -90,18 +90,13 @@ export const FullNodeViewer = ({node, className}: FullNodeViewerProps) => {
return <div className="error">{i18n('no-data')}</div>;
}

const memoryUsed = isNumeric(node.MemoryUsed) ? Number(node.MemoryUsed) : undefined;
const memoryLimit = isNumeric(node.MemoryLimit) ? Number(node.MemoryLimit) : undefined;
const hasMemoryUsed =
memoryUsed !== undefined && Number.isFinite(memoryUsed) && memoryUsed >= 0;
const hasMemoryLimit =
memoryLimit !== undefined && Number.isFinite(memoryLimit) && memoryLimit > 0;
const {memoryUsed, memoryLimit} = getNodeMemory(node);

const renderMemory = () => {
if (!hasMemoryUsed) {
if (memoryUsed === undefined) {
return EMPTY_DATA_PLACEHOLDER;
}
if (!hasMemoryLimit) {
if (memoryLimit === undefined) {
return formatMemoryValues(memoryUsed)[0];
}

Expand Down
24 changes: 19 additions & 5 deletions src/components/MemoryViewer/MemoryViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import {DefinitionList, Flex, useTheme} from '@gravity-ui/uikit';
import type {TMemoryStats} from '../../types/api/nodes';
import {formatBytes} from '../../utils/bytesParsers';
import {cn} from '../../utils/cn';
import {GIGABYTE} from '../../utils/constants';
import {EMPTY_DATA_PLACEHOLDER, GIGABYTE} from '../../utils/constants';
import {getNodeMemory} from '../../utils/memory';
import type {FormatProgressViewerValues} from '../../utils/progress';
import {calculateProgressStatus} from '../../utils/progress';
import {isNumeric} from '../../utils/utils';
import {HoverPopup} from '../HoverPopup/HoverPopup';
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';

import {calculateAllocatedMemory, getMemorySegments} from './utils';
import {getMemorySegments} from './utils';

import './MemoryViewer.scss';

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

export interface MemoryProgressViewerProps {
stats: TMemoryStats;
memoryUsed?: string;
memoryLimit?: string;
className?: string;
warningThreshold?: number;
dangerThreshold?: number;
Expand All @@ -47,17 +50,28 @@ export interface MemoryProgressViewerProps {

export function MemoryViewer({
stats,
memoryUsed: fallbackMemoryUsed,
memoryLimit: fallbackMemoryLimit,
percents,
formatValues,
className,
warningThreshold,
dangerThreshold,
}: MemoryProgressViewerProps) {
const memoryUsage = stats.AnonRss ?? calculateAllocatedMemory(stats);
const {memoryUsed: memoryUsage, memoryLimit: capacity} = getNodeMemory({
MemoryStats: stats,
MemoryUsed: fallbackMemoryUsed,
MemoryLimit: fallbackMemoryLimit,
});
Comment thread
Raubzeug marked this conversation as resolved.
const theme = useTheme();

const capacity = stats.HardLimit;
if (memoryUsage === undefined) {
return EMPTY_DATA_PLACEHOLDER;
}
if (capacity === undefined) {
return formatValues(memoryUsage)[0];
}
Comment thread
Raubzeug marked this conversation as resolved.
Outdated

const theme = useTheme();
let fillWidth =
Math.floor((parseFloat(String(memoryUsage)) / parseFloat(String(capacity))) * 100) || 0;
fillWidth = fillWidth > 100 ? 100 : fillWidth;
Expand Down
6 changes: 0 additions & 6 deletions src/components/MemoryViewer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ import {isNumeric} from '../../utils/utils';

import i18n from './i18n';

export function calculateAllocatedMemory(stats: TMemoryStats) {
const allocatedMemory = getMaybeNumber(stats.AllocatedMemory) || 0;
const allocatorCaches = getMaybeNumber(stats.AllocatorCachesMemory) || 0;
return String(allocatedMemory + allocatorCaches);
}

function getMaybeNumber(value: string | number | undefined): number | undefined {
return isNumeric(value) ? parseFloat(String(value)) : undefined;
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/nodesColumns/__test__/constants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ describe('storage nodes capacity column contracts', () => {
expect(getNodesColumnSortField(columnId)).toBe(expectedSortField);
});
});

test('requests detailed memory when only RAM is selected', () => {
expect(getRequiredDataFields([NODES_COLUMNS_IDS.RAM], NODES_COLUMNS_TO_DATA_FIELDS)).toEqual([
'Memory',
'MemoryDetailed',
]);
});
38 changes: 26 additions & 12 deletions src/components/nodesColumns/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
formatStorageValuesToGb,
} from '../../utils/dataFormatters/dataFormatters';
import {getUsageSeverity} from '../../utils/generateEvaluator';
import {getNodeMemory} from '../../utils/memory';
import type {Column} from '../../utils/tableUtils/types';
import {formatToMs, parseUsToMs} from '../../utils/timeParsers';
import {bytesToSpeed, isNumeric} from '../../utils/utils';
Expand Down Expand Up @@ -169,22 +170,26 @@ export function getUptimeColumn<
};
}

export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: string}>(): Column<T> {
export function getRAMColumn<
T extends {MemoryStats?: TMemoryStats; MemoryUsed?: string; MemoryLimit?: string},
>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.RAM,
header: NODES_COLUMNS_TITLES.RAM,
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
sortAccessor: (row) => getNodeMemory(row).memoryUsed ?? 0,
defaultOrder: DataTable.DESCENDING,
render: ({row}) => {
const {memoryUsed: used, memoryLimit: limit} = getNodeMemory(row);
const [memoryUsed, memoryLimit] = formatStorageValues(
isNumeric(row.MemoryUsed) ? Number(row.MemoryUsed) : undefined,
isNumeric(row.MemoryLimit) ? Number(row.MemoryLimit) : undefined,
used,
limit,
'gb',
undefined,
true,
);

const hasData = memoryUsed || memoryLimit;
const memoryContent = used === undefined ? EMPTY_DATA_PLACEHOLDER : memoryUsed;

return (
<CellWithPopover
Expand All @@ -206,10 +211,10 @@ export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: strin
</DefinitionList>
}
>
{isNumeric(row.MemoryUsed) ? (
{used !== undefined && limit !== undefined ? (
<ProgressViewer
value={row.MemoryUsed}
capacity={row.MemoryLimit}
value={used}
capacity={limit}
formatValues={(value, total) =>
formatStorageValues(value, total, 'gb', undefined, true)
}
Expand All @@ -218,7 +223,7 @@ export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: strin
hideCapacity
/>
) : (
EMPTY_DATA_PLACEHOLDER
memoryContent
)}
</CellWithPopover>
);
Expand All @@ -238,18 +243,27 @@ export function getMemoryColumn<
render: ({row}) => {
if (row.MemoryStats) {
return (
<MemoryViewer formatValues={formatStorageValuesToGb} stats={row.MemoryStats} />
<MemoryViewer
formatValues={formatStorageValuesToGb}
stats={row.MemoryStats}
memoryUsed={row.MemoryUsed}
memoryLimit={row.MemoryLimit}
/>
);
}

if (!isNumeric(row.MemoryUsed)) {
const {memoryUsed, memoryLimit} = getNodeMemory(row);
if (memoryUsed === undefined) {
return EMPTY_DATA_PLACEHOLDER;
}
if (memoryLimit === undefined) {
return formatStorageValuesToGb(memoryUsed)[0];
}

return (
<ProgressViewer
value={row.MemoryUsed}
capacity={row.MemoryLimit}
value={memoryUsed}
capacity={memoryLimit}
formatValues={formatStorageValuesToGb}
colorizeProgress={true}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/nodesColumns/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record<NodesColumnId, NodesRequiredFi
Version: ['Version'],
Uptime: ['Uptime', 'DisconnectTime'],
Memory: ['Memory', 'MemoryDetailed'],
RAM: ['Memory'],
RAM: ['Memory', 'MemoryDetailed'],
Comment thread
Raubzeug marked this conversation as resolved.
Pools: ['CPU'],
CPU: ['CPU'],
LoadAverage: ['LoadAverage'],
Expand Down
2 changes: 1 addition & 1 deletion src/containers/Versions/Versions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface VersionsContainerProps {
export function VersionsContainer({cluster, loading}: VersionsContainerProps) {
const [autoRefreshInterval] = useAutoRefreshInterval();
const {currentData, isLoading: isNodesLoading} = nodesApi.useGetNodesQuery(
{tablets: false, fieldsRequired: ['SystemState', 'SubDomainKey']},
{tablets: false, fieldsRequired: ['SystemState', 'SubDomainKey', 'MemoryDetailed']},
{pollingInterval: autoRefreshInterval},
);
const versionsDataMap = useVersionsDataMap(cluster);
Expand Down
85 changes: 85 additions & 0 deletions src/utils/__test__/memory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {getNodeMemory} from '../memory';

describe('getNodeMemory', () => {
test('uses the process consumption and effective YDB limit instead of host memory', () => {
expect(
getNodeMemory({
MemoryUsed: '21474836480',
MemoryLimit: '34359738368',
MemoryStats: {AnonRss: '21474836480', HardLimit: '25769803776'},
}),
).toEqual({memoryUsed: 21474836480, memoryLimit: 25769803776});
});

test('prefers anonymous RSS over allocator and legacy usage, including zero', () => {
expect(
getNodeMemory({
MemoryUsed: '200',
MemoryStats: {AnonRss: '0', AllocatedMemory: '100', AllocatorCachesMemory: '20'},
}),
).toEqual({memoryUsed: 0, memoryLimit: undefined});
});

test('includes allocator caches when anonymous RSS is unavailable', () => {
expect(
getNodeMemory({
MemoryUsed: '100',
MemoryStats: {AllocatedMemory: '100', AllocatorCachesMemory: '20'},
}),
).toEqual({memoryUsed: 120, memoryLimit: undefined});
});

test.each([{AllocatedMemory: '100'}, {AllocatorCachesMemory: '100'}])(
'uses the available allocator counters: %j',
(MemoryStats) => {
expect(getNodeMemory({MemoryStats}).memoryUsed).toBe(100);
},
);

test.each([undefined, {}, {HardLimit: '250'}])(
'preserves legacy usage when detailed consumption is absent: %j',
(MemoryStats) => {
expect(getNodeMemory({MemoryUsed: '100', MemoryLimit: '250', MemoryStats})).toEqual({
memoryUsed: 100,
memoryLimit: 250,
});
},
);

test.each(['', ' ', 'invalid', '-1', 'Infinity'])('ignores invalid stats: %j', (value) => {
expect(
getNodeMemory({
MemoryUsed: '100',
MemoryLimit: '250',
MemoryStats: {
AnonRss: value,
AllocatedMemory: value,
AllocatorCachesMemory: value,
HardLimit: value,
},
}),
).toEqual({memoryUsed: 100, memoryLimit: 250});
});

test('falls back to the legacy limit for a zero hard limit', () => {
expect(getNodeMemory({MemoryLimit: '250', MemoryStats: {HardLimit: '0'}}).memoryLimit).toBe(
250,
);
});

test.each([undefined, '', 'invalid', '-1', 'Infinity'])(
'keeps unknown usage absent: %j',
(value) => {
expect(getNodeMemory({MemoryUsed: value, MemoryStats: {}}).memoryUsed).toBeUndefined();
},
);

test.each([undefined, '', 'invalid', '0', '-1', 'Infinity'])(
'keeps unknown limits absent: %j',
(value) => {
expect(
getNodeMemory({MemoryLimit: value, MemoryStats: {HardLimit: value}}).memoryLimit,
).toBeUndefined();
},
);
});
24 changes: 24 additions & 0 deletions src/utils/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type {TSystemStateInfo} from '../types/api/nodes';

import {parseOptionalNonNegativeNumber} from './utils';

export function getNodeMemory(
node: Pick<TSystemStateInfo, 'MemoryStats' | 'MemoryUsed' | 'MemoryLimit'>,
) {
const stats = node.MemoryStats;
const anonRss = parseOptionalNonNegativeNumber(stats?.AnonRss);
const allocated = parseOptionalNonNegativeNumber(stats?.AllocatedMemory);
const caches = parseOptionalNonNegativeNumber(stats?.AllocatorCachesMemory);
const allocatorUsage =
allocated !== undefined || caches !== undefined
? (allocated ?? 0) + (caches ?? 0)
: undefined;

const memoryUsed = anonRss ?? allocatorUsage ?? parseOptionalNonNegativeNumber(node.MemoryUsed);
const memoryLimit =
parseOptionalNonNegativeNumber(stats?.HardLimit) ||
parseOptionalNonNegativeNumber(node.MemoryLimit) ||
undefined;

return {memoryUsed, memoryLimit};
}
Loading
Loading