From b3c29d5a61d0fc54f78ccc08948c348d367b1d7c Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 4 Feb 2025 11:40:10 +0300 Subject: [PATCH] fix(PDiskSpaceDistribution): update slots severity calculation --- src/containers/PDiskPage/PDiskPage.tsx | 10 +- src/containers/PDiskPage/i18n/en.json | 2 +- .../preparePDiskDataResponse.test.ts | 227 ++++++++++++++++++ src/store/reducers/pdisk/utils.ts | 21 +- src/utils/disks/calculatePDiskSeverity.ts | 17 +- src/utils/disks/constants.ts | 2 +- src/utils/disks/helpers.ts | 9 +- 7 files changed, 263 insertions(+), 25 deletions(-) create mode 100644 src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx index 48963c8de..b1d26271d 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 df65b30aa..e7d3de747 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 000000000..00d7335e3 --- /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 41e9d8c00..e121dfd6e 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 d0f9f55a0..ecde637ba 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 e4e57c450..609c6c00f 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 8528e4cb6..84c35aac1 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;