diff --git a/src/components/CellWithPopover/CellWithPopover.scss b/src/components/CellWithPopover/CellWithPopover.scss
index 1d12c3ec40..6c0c428a73 100644
--- a/src/components/CellWithPopover/CellWithPopover.scss
+++ b/src/components/CellWithPopover/CellWithPopover.scss
@@ -16,5 +16,9 @@
.g-popover__handler {
display: inline;
}
+
+ &_full-width {
+ width: 100%;
+ }
}
}
diff --git a/src/components/CellWithPopover/CellWithPopover.tsx b/src/components/CellWithPopover/CellWithPopover.tsx
index 14529d8b42..a2daa0a577 100644
--- a/src/components/CellWithPopover/CellWithPopover.tsx
+++ b/src/components/CellWithPopover/CellWithPopover.tsx
@@ -9,6 +9,7 @@ const b = cn('ydb-cell-with-popover');
interface CellWithPopoverProps extends PopoverProps {
wrapperClassName?: string;
+ fullWidth?: boolean;
}
const DELAY_TIMEOUT = 100;
@@ -17,14 +18,15 @@ export function CellWithPopover({
children,
className,
wrapperClassName,
+ fullWidth,
...props
}: CellWithPopoverProps) {
return (
-
+
{children}
diff --git a/src/components/ProgressViewer/ProgressViewer.tsx b/src/components/ProgressViewer/ProgressViewer.tsx
index 6f33d6492e..2176de3cac 100644
--- a/src/components/ProgressViewer/ProgressViewer.tsx
+++ b/src/components/ProgressViewer/ProgressViewer.tsx
@@ -48,6 +48,7 @@ export interface ProgressViewerProps {
inverseColorize?: boolean;
warningThreshold?: number;
dangerThreshold?: number;
+ hideCapacity?: boolean;
}
export function ProgressViewer({
@@ -61,6 +62,7 @@ export function ProgressViewer({
inverseColorize,
warningThreshold = 60,
dangerThreshold = 80,
+ hideCapacity,
}: ProgressViewerProps) {
const theme = useTheme();
@@ -94,7 +96,7 @@ export function ProgressViewer({
};
const renderContent = () => {
- if (isNumeric(capacity)) {
+ if (isNumeric(capacity) && !hideCapacity) {
return `${valueText} ${divider} ${capacityText}`;
}
diff --git a/src/components/TooltipsContent/PoolTooltipContent/PoolTooltipContent.tsx b/src/components/TooltipsContent/PoolTooltipContent/PoolTooltipContent.tsx
index a25aa25aa0..c0973c4dd2 100644
--- a/src/components/TooltipsContent/PoolTooltipContent/PoolTooltipContent.tsx
+++ b/src/components/TooltipsContent/PoolTooltipContent/PoolTooltipContent.tsx
@@ -1,7 +1,7 @@
import type {TPoolStats} from '../../../types/api/nodes';
import {InfoViewer, createInfoFormatter, formatObject} from '../../InfoViewer';
-const formatPool = createInfoFormatter({
+export const formatPool = createInfoFormatter({
values: {
Usage: (value) => value && `${(Number(value) * 100).toFixed(2)} %`,
},
diff --git a/src/components/nodesColumns/NodesColumns.scss b/src/components/nodesColumns/NodesColumns.scss
new file mode 100644
index 0000000000..b342bcc119
--- /dev/null
+++ b/src/components/nodesColumns/NodesColumns.scss
@@ -0,0 +1,6 @@
+.ydb-nodes-columns {
+ &__column-ram,
+ &__column-cpu {
+ min-width: 40px;
+ }
+}
diff --git a/src/components/nodesColumns/columns.tsx b/src/components/nodesColumns/columns.tsx
index f726c7b864..e5a09a7805 100644
--- a/src/components/nodesColumns/columns.tsx
+++ b/src/components/nodesColumns/columns.tsx
@@ -1,24 +1,36 @@
import DataTable from '@gravity-ui/react-data-table';
+import {DefinitionList} from '@gravity-ui/uikit';
import {getLoadSeverityForNode} from '../../store/reducers/nodes/utils';
import type {TPoolStats} from '../../types/api/nodes';
import type {TTabletStateInfo} from '../../types/api/tablet';
import {valueIsDefined} from '../../utils';
+import {cn} from '../../utils/cn';
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
-import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters';
+import {
+ formatStorageValues,
+ formatStorageValuesToGb,
+} from '../../utils/dataFormatters/dataFormatters';
import {getSpaceUsageSeverity} from '../../utils/storage';
import type {Column} from '../../utils/tableUtils/types';
+import {isNumeric} from '../../utils/utils';
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
import {NodeHostWrapper} from '../NodeHostWrapper/NodeHostWrapper';
import type {NodeHostData} from '../NodeHostWrapper/NodeHostWrapper';
import {PoolsGraph} from '../PoolsGraph/PoolsGraph';
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
import {TabletsStatistic} from '../TabletsStatistic';
+import {formatPool} from '../TooltipsContent';
import {UsageLabel} from '../UsageLabel/UsageLabel';
import {NODES_COLUMNS_IDS, NODES_COLUMNS_TITLES} from './constants';
+import i18n from './i18n';
import type {GetNodesColumnsParams} from './types';
+import './NodesColumns.scss';
+
+const b = cn('ydb-nodes-columns');
+
export function getNodeIdColumn(): Column {
return {
name: NODES_COLUMNS_IDS.NodeId,
@@ -111,6 +123,57 @@ export function getMemoryColumn<
resizeMinWidth: 170,
};
}
+
+export function getRAMColumn(): Column {
+ return {
+ name: NODES_COLUMNS_IDS.RAM,
+ header: NODES_COLUMNS_TITLES.RAM,
+ sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
+ defaultOrder: DataTable.DESCENDING,
+ render: ({row}) => {
+ const [memoryUsed, memoryLimit] =
+ isNumeric(row.MemoryUsed) && isNumeric(row.MemoryLimit)
+ ? formatStorageValues(
+ Number(row.MemoryUsed),
+ Number(row.MemoryLimit),
+ 'gb',
+ undefined,
+ true,
+ )
+ : [0, 0];
+ return (
+
+
+ {memoryUsed}
+
+
+ {memoryLimit}
+
+
+ }
+ >
+
+ formatStorageValues(value, total, 'gb', undefined, true)
+ }
+ className={b('column-ram')}
+ colorizeProgress
+ hideCapacity
+ />
+
+ );
+ },
+ align: DataTable.LEFT,
+ width: 80,
+ resizeMinWidth: 40,
+ };
+}
export function getSharedCacheUsageColumn<
T extends {SharedCacheUsed?: string | number; SharedCacheLimit?: string | number},
>(): Column {
@@ -130,10 +193,10 @@ export function getSharedCacheUsageColumn<
resizeMinWidth: 170,
};
}
-export function getCpuColumn(): Column {
+export function getPoolsColumn(): Column {
return {
- name: NODES_COLUMNS_IDS.CPU,
- header: NODES_COLUMNS_TITLES.CPU,
+ name: NODES_COLUMNS_IDS.Pools,
+ header: NODES_COLUMNS_TITLES.Pools,
sortAccessor: ({PoolStats = []}) => Math.max(...PoolStats.map(({Usage}) => Number(Usage))),
defaultOrder: DataTable.DESCENDING,
render: ({row}) =>
@@ -143,6 +206,65 @@ export function getCpuColumn(): Column
resizeMinWidth: 60,
};
}
+export function getCpuColumn<
+ T extends {PoolStats?: TPoolStats[]; CoresUsed?: number; CoresTotal?: number},
+>(): Column {
+ return {
+ name: NODES_COLUMNS_IDS.CPU,
+ header: NODES_COLUMNS_TITLES.CPU,
+ sortAccessor: ({PoolStats = []}) => Math.max(...PoolStats.map(({Usage}) => Number(Usage))),
+ defaultOrder: DataTable.DESCENDING,
+ render: ({row}) => {
+ if (!row.PoolStats) {
+ return EMPTY_DATA_PLACEHOLDER;
+ }
+
+ let totalPoolUsage =
+ isNumeric(row.CoresUsed) && isNumeric(row.CoresTotal)
+ ? row.CoresUsed / row.CoresTotal
+ : undefined;
+
+ if (totalPoolUsage === undefined) {
+ let totalThreadsCount = 0;
+ totalPoolUsage = row.PoolStats.reduce((acc, pool) => {
+ totalThreadsCount += Number(pool.Threads);
+ return acc + Number(pool.Usage) * Number(pool.Threads);
+ }, 0);
+
+ totalPoolUsage = totalPoolUsage / totalThreadsCount;
+ }
+
+ return (
+
+ {row.PoolStats.map((pool) =>
+ isNumeric(pool.Usage) ? (
+
+ {formatPool('Usage', pool.Usage).value}
+
+ ) : null,
+ )}
+
+ }
+ >
+
+
+ );
+ },
+ align: DataTable.LEFT,
+ width: 80,
+ resizeMinWidth: 40,
+ };
+}
export function getLoadAverageColumn(): Column {
return {
name: NODES_COLUMNS_IDS.LoadAverage,
diff --git a/src/components/nodesColumns/constants.ts b/src/components/nodesColumns/constants.ts
index 97813b90f7..c3a53b990d 100644
--- a/src/components/nodesColumns/constants.ts
+++ b/src/components/nodesColumns/constants.ts
@@ -14,7 +14,9 @@ export const NODES_COLUMNS_IDS = {
Version: 'Version',
Uptime: 'Uptime',
Memory: 'Memory',
+ RAM: 'RAM',
CPU: 'CPU',
+ Pools: 'Pools',
LoadAverage: 'LoadAverage',
Load: 'Load',
DiskSpaceUsage: 'DiskSpaceUsage',
@@ -54,6 +56,12 @@ export const NODES_COLUMNS_TITLES = {
get Memory() {
return i18n('memory');
},
+ get RAM() {
+ return i18n('ram');
+ },
+ get Pools() {
+ return i18n('pools');
+ },
get CPU() {
return i18n('cpu');
},
@@ -94,6 +102,8 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record(),
getUptimeColumn(),
getMemoryColumn(),
+ getRAMColumn(),
+ getPoolsColumn(),
getCpuColumn(),
getLoadAverageColumn(),
getTabletsColumn(params),
diff --git a/src/containers/Nodes/columns/constants.ts b/src/containers/Nodes/columns/constants.ts
index 5ebff16cca..c0c701cf6e 100644
--- a/src/containers/Nodes/columns/constants.ts
+++ b/src/containers/Nodes/columns/constants.ts
@@ -10,7 +10,7 @@ export const DEFAULT_NODES_COLUMNS: NodesColumnId[] = [
'Version',
'Uptime',
'Memory',
- 'CPU',
+ 'Pools',
'LoadAverage',
'Tablets',
];
diff --git a/src/containers/Nodes/getNodes.ts b/src/containers/Nodes/getNodes.ts
index 7fd7a11efc..20d7458f28 100644
--- a/src/containers/Nodes/getNodes.ts
+++ b/src/containers/Nodes/getNodes.ts
@@ -5,6 +5,7 @@ import {prepareNodesData} from '../../store/reducers/nodes/utils';
import type {NodesRequestParams} from '../../types/api/nodes';
import {prepareSortValue} from '../../utils/filters';
import {
+ NODES_SORT_VALUE_TO_FIELD,
getProblemParamValue,
getUptimeParamValue,
isSortableNodesProperty,
@@ -35,7 +36,7 @@ export const getNodes: FetchData<
const {path, database, searchValue, problemFilter, uptimeFilter} = filters ?? {};
const sort = isSortableNodesProperty(columnId)
- ? prepareSortValue(columnId, sortOrder)
+ ? prepareSortValue(NODES_SORT_VALUE_TO_FIELD[columnId], sortOrder)
: undefined;
const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);
diff --git a/src/containers/Storage/StorageNodes/columns/columns.tsx b/src/containers/Storage/StorageNodes/columns/columns.tsx
index 5f72e41231..3a15a42659 100644
--- a/src/containers/Storage/StorageNodes/columns/columns.tsx
+++ b/src/containers/Storage/StorageNodes/columns/columns.tsx
@@ -9,6 +9,8 @@ import {
getMissingDisksColumn,
getNodeIdColumn,
getNodeNameColumn,
+ getPoolsColumn,
+ getRAMColumn,
getRackColumn,
getUptimeColumn,
getVersionColumn,
@@ -72,6 +74,8 @@ export const getStorageNodesColumns = ({
getRackColumn(),
getVersionColumn(),
getMemoryColumn(),
+ getRAMColumn(),
+ getPoolsColumn(),
getCpuColumn(),
getDiskSpaceUsageColumn(),
getUptimeColumn(),
diff --git a/src/containers/Storage/StorageNodes/columns/constants.ts b/src/containers/Storage/StorageNodes/columns/constants.ts
index 30e81ca734..18b7920539 100644
--- a/src/containers/Storage/StorageNodes/columns/constants.ts
+++ b/src/containers/Storage/StorageNodes/columns/constants.ts
@@ -13,7 +13,7 @@ export const DEFAULT_STORAGE_NODES_COLUMNS: NodesColumnId[] = [
'Host',
'DC',
'Rack',
- 'CPU',
+ 'Pools',
'Uptime',
'PDisks',
];
diff --git a/src/containers/Storage/StorageNodes/getNodes.ts b/src/containers/Storage/StorageNodes/getNodes.ts
index 86ca308b54..53b8978070 100644
--- a/src/containers/Storage/StorageNodes/getNodes.ts
+++ b/src/containers/Storage/StorageNodes/getNodes.ts
@@ -7,7 +7,11 @@ import type {
import {prepareStorageNodesResponse} from '../../../store/reducers/storage/utils';
import type {NodesRequestParams} from '../../../types/api/nodes';
import {prepareSortValue} from '../../../utils/filters';
-import {getUptimeParamValue, isSortableNodesProperty} from '../../../utils/nodes';
+import {
+ NODES_SORT_VALUE_TO_FIELD,
+ getUptimeParamValue,
+ isSortableNodesProperty,
+} from '../../../utils/nodes';
import {getRequiredDataFields} from '../../../utils/tableUtils/getRequiredDataFields';
export const getStorageNodes: FetchData<
@@ -37,7 +41,7 @@ export const getStorageNodes: FetchData<
const {sortOrder, columnId} = sortParams ?? {};
const sort = isSortableNodesProperty(columnId)
- ? prepareSortValue(columnId, sortOrder)
+ ? prepareSortValue(NODES_SORT_VALUE_TO_FIELD[columnId], sortOrder)
: undefined;
const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);
diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx
index 00b7646246..079a2a3039 100644
--- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx
+++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx
@@ -1,9 +1,9 @@
import type {Column} from '@gravity-ui/react-data-table';
import {
- getCpuColumn,
getHostColumn,
getNodeIdColumn,
+ getPoolsColumn,
} from '../../../../../components/nodesColumns/columns';
import {
NODES_COLUMNS_TO_DATA_FIELDS,
@@ -29,7 +29,7 @@ export function getTopNodesByCpuColumns(
const hostColumn = {...getHostColumn(params), width: undefined};
const columns = [
- getCpuColumn(),
+ getPoolsColumn(),
getNodeIdColumn(),
hostColumn,
];
diff --git a/src/types/api/nodes.ts b/src/types/api/nodes.ts
index 8a3e63a456..be4b5e2871 100644
--- a/src/types/api/nodes.ts
+++ b/src/types/api/nodes.ts
@@ -77,6 +77,8 @@ export interface TSystemStateInfo {
/** double */
MaxDiskUsage?: number;
Location?: TNodeLocation;
+ CoresUsed?: number;
+ CoresTotal?: number;
/**
* int64
@@ -193,7 +195,9 @@ export type NodesSortValue =
| 'Memory'
| `Missing`
| `DiskSpaceUsage`
- | `Database`;
+ | `Database`
+ | 'Pools'
+ | 'RAM';
export type NodesSort = BackendSortParam;
diff --git a/src/utils/nodes.ts b/src/utils/nodes.ts
index 7ee61283f9..f7d63ad862 100644
--- a/src/utils/nodes.ts
+++ b/src/utils/nodes.ts
@@ -115,7 +115,28 @@ export const NODES_SORT_VALUES: NodesSortValue[] = [
`Missing`,
`DiskSpaceUsage`,
`Database`,
+ 'Pools',
+ 'RAM',
];
+// Maps column names to actual fields on backend for sorting.
+export const NODES_SORT_VALUE_TO_FIELD: Record = {
+ NodeId: 'NodeId',
+ Host: 'Host',
+ NodeName: 'NodeName',
+ DC: 'DC',
+ Rack: 'Rack',
+ Version: 'Version',
+ Uptime: 'Uptime',
+ CPU: 'CPU',
+ LoadAverage: 'LoadAverage',
+ Memory: 'Memory',
+ Missing: 'Missing',
+ DiskSpaceUsage: 'DiskSpaceUsage',
+ Database: 'Database',
+ Pools: 'CPU',
+ RAM: 'Memory',
+};
+
export const isSortableNodesProperty = (value: unknown): value is NodesSortValue =>
NODES_SORT_VALUES.includes(value as NodesSortValue);
diff --git a/tests/suites/nodes/nodes.test.ts b/tests/suites/nodes/nodes.test.ts
index fd895e4668..d4de19d821 100644
--- a/tests/suites/nodes/nodes.test.ts
+++ b/tests/suites/nodes/nodes.test.ts
@@ -127,7 +127,7 @@ test.describe('Test Nodes Paginated Table', async () => {
expect(rowData).toHaveProperty('Host');
expect(rowData).toHaveProperty('Uptime');
expect(rowData).toHaveProperty('Memory');
- expect(rowData).toHaveProperty('CPU');
+ expect(rowData).toHaveProperty('Pools');
});
test('Column values can be retrieved correctly', async ({page}) => {