diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx
index 48963c8de6..b1d26271dd 100644
--- a/src/containers/PDiskPage/PDiskPage.tsx
+++ b/src/containers/PDiskPage/PDiskPage.tsx
@@ -37,15 +37,15 @@ import './PDiskPage.scss';
const pdiskPageCn = cn('ydb-pdisk-page');
const PDISK_TABS_IDS = {
- diskDistribution: 'diskDistribution',
+ spaceDistribution: 'spaceDistribution',
storage: 'storage',
} as const;
const PDISK_PAGE_TABS = [
{
- id: PDISK_TABS_IDS.diskDistribution,
+ id: PDISK_TABS_IDS.spaceDistribution,
get title() {
- return pDiskPageKeyset('disk-distribution');
+ return pDiskPageKeyset('space-distribution');
},
},
{
@@ -56,7 +56,7 @@ const PDISK_PAGE_TABS = [
},
];
-const pDiskTabSchema = z.nativeEnum(PDISK_TABS_IDS).catch(PDISK_TABS_IDS.diskDistribution);
+const pDiskTabSchema = z.nativeEnum(PDISK_TABS_IDS).catch(PDISK_TABS_IDS.spaceDistribution);
export function PDiskPage() {
const dispatch = useTypedDispatch();
@@ -237,7 +237,7 @@ export function PDiskPage() {
const renderTabsContent = () => {
switch (pDiskTab) {
- case 'diskDistribution': {
+ case 'spaceDistribution': {
return pDiskData ? (
diff --git a/src/containers/PDiskPage/i18n/en.json b/src/containers/PDiskPage/i18n/en.json
index df65b30aa1..e7d3de7475 100644
--- a/src/containers/PDiskPage/i18n/en.json
+++ b/src/containers/PDiskPage/i18n/en.json
@@ -4,7 +4,7 @@
"node": "Node",
"storage": "Storage",
- "disk-distribution": "Disk distribution",
+ "space-distribution": "Space distribution",
"empty-slot": "Empty slot",
"log": "Log",
diff --git a/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts b/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts
new file mode 100644
index 0000000000..00d7335e3a
--- /dev/null
+++ b/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts
@@ -0,0 +1,227 @@
+import type {TPDiskInfoResponse} from '../../../../types/api/pdisk';
+import {preparePDiskDataResponse} from '../utils';
+
+describe('preparePDiskDataResponse', () => {
+ const rawData = {
+ Whiteboard: {
+ PDisk: {
+ PDiskId: 1,
+ ChangeTime: '1738670321505',
+ Path: '/ydb_data/pdisk1tmbtrl7c.data',
+ Guid: '1',
+ Category: '0',
+ AvailableSize: '66454552576',
+ TotalSize: '100000000000',
+ State: 'Normal',
+ Device: 'Green',
+ Realtime: 'Green',
+ SerialNumber: '',
+ SystemSize: '2000000000',
+ LogUsedSize: '2000000000',
+ LogTotalSize: '60000000000',
+ EnforcedDynamicSlotSize: '20000000000',
+ NumActiveSlots: 1,
+ },
+ VDisks: [
+ {
+ VDiskId: {
+ GroupID: 2181038081,
+ GroupGeneration: 1,
+ Ring: 0,
+ Domain: 0,
+ VDisk: 0,
+ },
+ ChangeTime: '1738672482849',
+ PDiskId: 1,
+ VDiskSlotId: 1001,
+ Guid: '1',
+ Kind: '0',
+ VDiskState: 'OK',
+ DiskSpace: 'Green',
+ SatisfactionRank: {
+ FreshRank: {
+ Flag: 'Green',
+ },
+ LevelRank: {
+ Flag: 'Green',
+ },
+ },
+ Replicated: true,
+ ReplicationProgress: 1,
+ ReplicationSecondsRemaining: 0,
+ AllocatedSize: '5000000000',
+ AvailableSize: '20000000000',
+ HasUnreadableBlobs: false,
+ IncarnationGuid: '13331041715376219418',
+ InstanceGuid: '18177015420302975983',
+ FrontQueues: 'Green',
+ StoragePoolName: 'dynamic_storage_pool:1',
+ ReadThroughput: '0',
+ WriteThroughput: '0',
+ },
+ ],
+ },
+ BSC: {
+ PDisk: {
+ Type: 'ROT',
+ Kind: '0',
+ Path: '/ydb_data/pdisk1tmbtrl7c.data',
+ Guid: '1',
+ BoxId: '1',
+ SharedWithOs: false,
+ ReadCentric: false,
+ AvailableSize: '66454552576',
+ TotalSize: '68719476736',
+ StatusV2: 'ACTIVE',
+ StatusChangeTimestamp: '1737458853219782',
+ EnforcedDynamicSlotSize: '20000000000',
+ ExpectedSlotCount: 16,
+ NumActiveSlots: 1,
+ Category: '0',
+ DecommitStatus: 'DECOMMIT_NONE',
+ State: 'Normal',
+ },
+ VDisks: [
+ {
+ Key: {
+ NodeId: 1,
+ PDiskId: 1,
+ VSlotId: 0,
+ },
+ Info: {
+ GroupId: 0,
+ GroupGeneration: 1,
+ FailRealm: 0,
+ FailDomain: 0,
+ VDisk: 0,
+ AllocatedSize: '1000000000',
+ AvailableSize: '20000000000',
+ StatusV2: 'READY',
+ Kind: 'Default',
+ DiskSpace: 'Green',
+ Replicated: true,
+ State: 'OK',
+ },
+ },
+ ],
+ },
+ } as unknown as TPDiskInfoResponse;
+
+ it('Should correctly retrieve slots', () => {
+ const preparedData = preparePDiskDataResponse([rawData, {}]);
+
+ expect(preparedData.SlotItems?.length).toEqual(17);
+ expect(preparedData.SlotItems?.filter((slot) => slot.SlotType === 'log').length).toEqual(1);
+ expect(preparedData.SlotItems?.filter((slot) => slot.SlotType === 'vDisk').length).toEqual(
+ 1,
+ );
+ expect(preparedData.SlotItems?.filter((slot) => slot.SlotType === 'empty').length).toEqual(
+ 15,
+ );
+ });
+ it('Should correctly calculate empty slots size if EnforcedDynamicSlotSize is provided', () => {
+ const preparedData = preparePDiskDataResponse([rawData, {}]);
+
+ expect(preparedData.SlotItems?.find((slot) => slot.SlotType === 'empty')?.Total).toEqual(
+ 20_000_000_000,
+ );
+ });
+ it('Should correctly calculate empty slots size if EnforcedDynamicSlotSize is undefined', () => {
+ const data: TPDiskInfoResponse = {
+ ...rawData,
+ Whiteboard: {
+ ...rawData.Whiteboard,
+ PDisk: {
+ ...rawData.Whiteboard?.PDisk,
+ EnforcedDynamicSlotSize: undefined,
+ },
+ },
+ BSC: {
+ ...rawData.BSC,
+ PDisk: {
+ ...rawData.BSC?.PDisk,
+ EnforcedDynamicSlotSize: undefined,
+ },
+ },
+ };
+ const preparedData = preparePDiskDataResponse([data, {}]);
+
+ expect(preparedData.SlotItems?.find((slot) => slot.SlotType === 'empty')?.Total).toEqual(
+ 1_000_000_000,
+ );
+ });
+ it('Should return yellow or red severity for log if its size exceeds thresholds', () => {
+ const dataWarning: TPDiskInfoResponse = {
+ ...rawData,
+ Whiteboard: {
+ ...rawData.Whiteboard,
+ PDisk: {
+ ...rawData.Whiteboard?.PDisk,
+ LogUsedSize: '90',
+ LogTotalSize: '100',
+ },
+ },
+ };
+ const preparedDataWarning = preparePDiskDataResponse([dataWarning, {}]);
+
+ expect(
+ preparedDataWarning.SlotItems?.find((slot) => slot.SlotType === 'log')?.Severity,
+ ).toEqual(3);
+
+ const dataDanger: TPDiskInfoResponse = {
+ ...rawData,
+ Whiteboard: {
+ ...rawData.Whiteboard,
+ PDisk: {
+ ...rawData.Whiteboard?.PDisk,
+ LogUsedSize: '99',
+ LogTotalSize: '100',
+ },
+ },
+ };
+ const preparedDataDanger = preparePDiskDataResponse([dataDanger, {}]);
+
+ expect(
+ preparedDataDanger.SlotItems?.find((slot) => slot.SlotType === 'log')?.Severity,
+ ).toEqual(5);
+ });
+ it('Should return yellow or red severity for vdisk if its size exceeds thresholds', () => {
+ const dataWarning: TPDiskInfoResponse = {
+ ...rawData,
+ Whiteboard: {
+ ...rawData.Whiteboard,
+ VDisks: [
+ {
+ ...rawData.Whiteboard?.VDisks?.[0],
+ AllocatedSize: '90',
+ AvailableSize: '10',
+ },
+ ],
+ },
+ };
+ const preparedDataWarning = preparePDiskDataResponse([dataWarning, {}]);
+
+ expect(
+ preparedDataWarning.SlotItems?.find((slot) => slot.SlotType === 'vDisk')?.Severity,
+ ).toEqual(3);
+
+ const dataDanger: TPDiskInfoResponse = {
+ ...rawData,
+ Whiteboard: {
+ ...rawData.Whiteboard,
+ VDisks: [
+ {
+ ...rawData.Whiteboard?.VDisks?.[0],
+ AllocatedSize: '99',
+ AvailableSize: '1',
+ },
+ ],
+ },
+ };
+ const preparedDataDanger = preparePDiskDataResponse([dataDanger, {}]);
+
+ expect(
+ preparedDataDanger.SlotItems?.find((slot) => slot.SlotType === 'vDisk')?.Severity,
+ ).toEqual(5);
+ });
+});
diff --git a/src/store/reducers/pdisk/utils.ts b/src/store/reducers/pdisk/utils.ts
index 41e9d8c005..e121dfd6e7 100644
--- a/src/store/reducers/pdisk/utils.ts
+++ b/src/store/reducers/pdisk/utils.ts
@@ -1,6 +1,7 @@
import type {TPDiskInfoResponse} from '../../../types/api/pdisk';
import type {TEvSystemStateResponse} from '../../../types/api/systemState';
import {getArray, valueIsDefined} from '../../../utils';
+import {getSpaceSeverity} from '../../../utils/disks/helpers';
import {
prepareWhiteboardPDiskData,
prepareWhiteboardVDiskData,
@@ -34,18 +35,20 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
TotalSize: PDiskTotalSize,
SystemSize,
ExpectedSlotCount,
- EnforcedDynamicSlotSize,
+ SlotSize,
} = preparedPDisk;
let logSlot: SlotItem<'log'> | undefined;
if (valueIsDefined(LogTotalSize)) {
+ const usagePercent = (Number(LogUsedSize) * 100) / Number(LogTotalSize);
+
logSlot = {
SlotType: 'log',
Used: Number(LogUsedSize),
Total: Number(LogTotalSize),
- UsagePercent: (Number(LogUsedSize) * 100) / Number(LogTotalSize),
- Severity: 1,
+ UsagePercent: usagePercent,
+ Severity: getSpaceSeverity(usagePercent),
SlotData: {
LogUsedSize,
LogTotalSize,
@@ -60,11 +63,19 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
preparedVDisks.sort((disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId));
const vdisksSlots: SlotItem<'vDisk'>[] = preparedVDisks.map((preparedVDisk) => {
+ // VDisks do not have strict limits and can be bigger than they should
+ // In most storage views we don't colorize them depending on space usage
+ // Colorize them inside PDiskSpaceDistribution so overused slots will be visible
+ const slotSeverity = Math.max(
+ getSpaceSeverity(preparedVDisk.AllocatedPercent),
+ preparedVDisk.Severity || 0,
+ );
+
return {
SlotType: 'vDisk',
Id: preparedVDisk.VDiskId?.GroupID,
Title: preparedVDisk.StoragePoolName,
- Severity: preparedVDisk.Severity,
+ Severity: slotSeverity,
Used: Number(preparedVDisk.AllocatedSize),
Total: Number(preparedVDisk.TotalSize),
UsagePercent: preparedVDisk.AllocatedPercent,
@@ -78,7 +89,7 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [
if (ExpectedSlotCount && ExpectedSlotCount > vdisksSlots.length) {
const emptySlotsCount = ExpectedSlotCount - vdisksSlots.length;
- let emptySlotSize = Number(EnforcedDynamicSlotSize);
+ let emptySlotSize = Number(SlotSize);
if (isNaN(emptySlotSize)) {
const vDisksTotalSize = vdisksSlots.reduce((sum, item) => {
diff --git a/src/utils/disks/calculatePDiskSeverity.ts b/src/utils/disks/calculatePDiskSeverity.ts
index d0f9f55a0d..ecde637ba4 100644
--- a/src/utils/disks/calculatePDiskSeverity.ts
+++ b/src/utils/disks/calculatePDiskSeverity.ts
@@ -1,14 +1,7 @@
-import {EFlag} from '../../types/api/enums';
import type {TPDiskState} from '../../types/api/pdisk';
-import {generateEvaluator} from '../generateEvaluator';
-import {
- DISK_COLOR_STATE_TO_NUMERIC_SEVERITY,
- NOT_AVAILABLE_SEVERITY,
- PDISK_STATE_SEVERITY,
-} from './constants';
-
-const getUsageSeverityForPDisk = generateEvaluator([EFlag.Green, EFlag.Yellow, EFlag.Red]);
+import {NOT_AVAILABLE_SEVERITY, PDISK_STATE_SEVERITY} from './constants';
+import {getSpaceSeverity} from './helpers';
export function calculatePDiskSeverity<
T extends {
@@ -17,13 +10,13 @@ export function calculatePDiskSeverity<
},
>(pDisk: T) {
const stateSeverity = getStateSeverity(pDisk.State);
- const spaceSeverityFlag = getUsageSeverityForPDisk(pDisk.AllocatedPercent || 0);
+ const spaceSeverity = getSpaceSeverity(pDisk.AllocatedPercent);
- if (stateSeverity === NOT_AVAILABLE_SEVERITY || !spaceSeverityFlag) {
+ if (stateSeverity === NOT_AVAILABLE_SEVERITY || !spaceSeverity) {
return stateSeverity;
}
- return Math.max(stateSeverity, DISK_COLOR_STATE_TO_NUMERIC_SEVERITY[spaceSeverityFlag]);
+ return Math.max(stateSeverity, spaceSeverity);
}
function getStateSeverity(pDiskState?: TPDiskState) {
diff --git a/src/utils/disks/constants.ts b/src/utils/disks/constants.ts
index e4e57c4500..609c6c00f5 100644
--- a/src/utils/disks/constants.ts
+++ b/src/utils/disks/constants.ts
@@ -10,7 +10,7 @@ export const DISK_COLOR_STATE_TO_NUMERIC_SEVERITY: Record
= {
Yellow: 3,
Orange: 4,
Red: 5,
-};
+} as const;
type SeverityToColor = Record;
diff --git a/src/utils/disks/helpers.ts b/src/utils/disks/helpers.ts
index 8528e4cb6d..84c35aac1a 100644
--- a/src/utils/disks/helpers.ts
+++ b/src/utils/disks/helpers.ts
@@ -1,6 +1,7 @@
import {valueIsDefined} from '..';
-import type {EFlag} from '../../types/api/enums';
+import {EFlag} from '../../types/api/enums';
import type {TVDiskStateInfo, TVSlotId} from '../../types/api/vdisk';
+import {generateEvaluator} from '../generateEvaluator';
import {
DISK_COLOR_STATE_TO_NUMERIC_SEVERITY,
@@ -15,6 +16,12 @@ export function isFullVDiskData(
return 'VDiskId' in disk;
}
+const getSpaceFlag = generateEvaluator([EFlag.Green, EFlag.Yellow, EFlag.Red]);
+
+export const getSpaceSeverity = (allocatedPercent?: number) => {
+ return valueIsDefined(allocatedPercent) ? getColorSeverity(getSpaceFlag(allocatedPercent)) : 0;
+};
+
export function getSeverityColor(severity: number | undefined) {
if (severity === undefined) {
return NOT_AVAILABLE_SEVERITY_COLOR;